ソースを参照

WiP(invite-ui): Initial move of invite UI to invite button (#1950)

* WiP(invite-ui): Initial move of invite UI to invite button

* Adjusts styling to fit both horizontal and vertical filmstrip

* Removes comment and functions not needed

* [squash] Addressing various review comments

* [squash] Move invite options to a separate config

* [squash] Adjust invite button styles until we fix the whole UI theme

* [squash] Fix the remote videos scroll

* [squash]:Do not show popup menu when 1 option is available

* [squash]: Disable the invite button in filmstrip mode

* feat(connection-indicator): implement automatic hiding on good connection (#2009)

* ref(connection-stats): use PropTypes package

* feat(connection-stats): display a summary of the connection quality

* feat(connection-indicator): show empty bars for interrupted connection

* feat(connection-indicator): change background color based on status

* feat(connection-indicator): implement automatic hiding on good connection

* fix(connection-indicator): explicitly set font size

Currently non-react code will set an icon size on ConnectionIndicator.
This doesn't work on initial call join in vertical filmstrip after
some changes to support hiding the indicator. The chosen fix is
passing in the icon size to mirror what would happe with full
filmstrip reactification.

* ref(connection-stats): rename statuses

* feat(connection-indicator): make hiding behavior configurable

The original implementation made the auto hiding of the indicator
configured in interfaceConfig.

* fix(connection-indicator): readd class expected by torture tests

* fix(connection-indicator): change connection quality display styling

Bold the connection summary in the stats popover so it stands out.
Change the summaries so there are only three--strong, nonoptimal,
poor.

* fix(connection-indicator): gray background on lost connection

* feat(icons): add new gsm bars icon

* feat(connection-indicator): use new 3-bar icon

* ref(icons): remove icon-connection and icon-connection-lost

Both have been replaced by icon-gsm-bars so they are not
being referenced anymore. Mobile looks to have connect-lost
as a separate icon in font-icons/jitsi.json.

* fix(defaultToolbarButtons): Fixes unresolved InfoDialogButton component problem

* [squash]: Makes invite button fit the container

* [squash]:Addressing invite truncate, remote menu position and comment

* [squash]:Fix z-index in horizontal mode, z-index in lonely call

* [squash]: Fix filmstripOnly property, remove important from css
master
yanas 7年前
コミット
86fcfcc535

+ 0
- 7
conference.js ファイルの表示

@@ -895,13 +895,6 @@ export default {
895 895
         let user = room.getParticipantById(id);
896 896
         return user && user.isModerator();
897 897
     },
898
-    /**
899
-     * Check if SIP is supported.
900
-     * @returns {boolean}
901
-     */
902
-    sipGatewayEnabled() {
903
-        return room.isSIPCallingSupported();
904
-    },
905 898
     get membersCount() {
906 899
         return room.getParticipants().length + 1;
907 900
     },

+ 50
- 5
css/_filmstrip.scss ファイルの表示

@@ -48,21 +48,66 @@
48 48
     &__videos {
49 49
         @extend %align-right;
50 50
         position:relative;
51
-        height:196px;
52 51
         padding: 0;
53 52
         /* The filmstrip should not be covered by the left toolbar. */
54 53
         bottom: 0;
55 54
         width:auto;
56 55
         transition: bottom 2s;
57 56
         overflow: visible !important;
58
-        /*!!! Removes the gap between the local video container and the remote
59
-        videos. */
60
-        font-size: 0pt;
61 57
 
62 58
         &#remoteVideos {
63 59
             border: $thumbnailsBorder solid transparent;
64 60
             padding-left: $defaultToolbarSize + 5;
65
-       }
61
+        }
62
+
63
+        /**
64
+         * The local video identifier.
65
+         */
66
+        &#filmstripLocalVideo {
67
+            bottom: 32px;
68
+            flex-direction: column;
69
+
70
+            /**
71
+             * The invite button style.
72
+             */
73
+            .filmstrip__invite {
74
+                padding-bottom: 5px;
75
+                margin-left: 2px;
76
+            }
77
+
78
+            /**
79
+             * The invite button group style.
80
+             * TOFIX: use AtlasKit.ButtonGroup if it starts supporting different
81
+             * flex grow options for the buttons.
82
+             */
83
+            .invite-button-group {
84
+                display: inline-flex;
85
+                justify-content: space-between;
86
+                width: 100%;
87
+
88
+                & button {
89
+                    background: $toolbarBackground;
90
+                    flex-grow: 1;
91
+                    flex-shrink: 1;
92
+                    overflow: hidden;
93
+                }
94
+
95
+                & > * {
96
+                    color: $toolbarButtonColor;
97
+                    flex-grow: 0;
98
+                    flex-shrink: 0;
99
+                    margin-left: 2px;
100
+                }
101
+
102
+                /**
103
+                 * Making sure any svg-s in an invite button group will be
104
+                 * colored the way we want.
105
+                 */
106
+                & path {
107
+                    fill: $toolbarButtonColor;
108
+                }
109
+            }
110
+        }
66 111
 
67 112
         &.hidden {
68 113
             bottom: -196px;

+ 20
- 19
css/_vertical_filmstrip_overrides.scss ファイルの表示

@@ -24,10 +24,6 @@
24 24
          */
25 25
         z-index: #{$tooltipsZ + 1};
26 26
 
27
-        &.hide-videos {
28
-            z-index: #{$tooltipsZ - 1};
29
-        }
30
-
31 27
         /**
32 28
          * Hide videos by making them slight to the right.
33 29
          */
@@ -50,9 +46,21 @@
50 46
             }
51 47
         }
52 48
 
49
+        /**
50
+         * Re-styles the local Video and invite button to better fit the
51
+         * vertical filmstrip layout.
52
+         */
53 53
         #filmstripLocalVideo {
54
+            bottom: 5px;
55
+            flex-direction: column-reverse;
54 56
             height: auto;
55
-            justify-content: flex-end;
57
+            justify-content: flex-start;
58
+
59
+            .filmstrip__invite {
60
+                padding-bottom: 0px;
61
+                padding-top: 5px;
62
+                z-index: $dropdownZ;
63
+            }
56 64
         }
57 65
 
58 66
         /**
@@ -69,10 +77,11 @@
69 77
             flex: 1;
70 78
             flex-direction: column;
71 79
             height: auto;
72
-            overflow-x: hidden !important;
80
+            justify-content: flex-end;
73 81
 
74
-            .remote-videos-container {
75
-                flex-direction: column;
82
+            #filmstripRemoteVideosContainer {
83
+                flex-direction: column-reverse;
84
+                overflow-x: hidden;
76 85
             }
77 86
         }
78 87
 
@@ -101,7 +110,7 @@
101 110
         }
102 111
 
103 112
         #remoteVideos {
104
-            flex-direction: column-reverse;
113
+            flex-direction: column;
105 114
             flex-grow: 1;
106 115
         }
107 116
 
@@ -153,15 +162,11 @@
153 162
      * be hidden.
154 163
      * The class opening is for when the filmstrip is transitioning from hidden
155 164
      * to visible.
156
-     * The class with-remote-videos is for when the filmstrip has remote videos
157
-     * displayed, as opposed to 1-on-1 mode where they might be hidden.
158
-     * The class without-remote-videos is for when the filmstrip is visible
159
-     * but it has no videos to display.
160 165
      */
161 166
     .video-state-indicator.moveToCorner {
162 167
         transition: right 0.5s;
163 168
 
164
-        &.with-filmstrip.with-remote-videos {
169
+        &.with-filmstrip {
165 170
             &#recordingLabel {
166 171
                 right: 200px;
167 172
             }
@@ -171,11 +176,7 @@
171 176
             }
172 177
         }
173 178
 
174
-        &.with-filmstrip.without-remote-videos {
175
-            transition-delay: 0.5s;
176
-        }
177
-
178
-        &.with-filmstrip.with-remote-videos.opening {
179
+        &.with-filmstrip.opening {
179 180
             transition: 0.9s;
180 181
             transition-timing-function: ease-in-out;
181 182
         }

+ 7
- 1
interface_config.js ファイルの表示

@@ -35,13 +35,14 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
35 35
         //main toolbar
36 36
         'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup', // jshint ignore:line
37 37
         //extended toolbar
38
-        'profile', 'addtocall', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
38
+        'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
39 39
     /**
40 40
      * Main Toolbar Buttons
41 41
      * All of them should be in TOOLBAR_BUTTONS
42 42
      */
43 43
     MAIN_TOOLBAR_BUTTONS: ['microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup'], // jshint ignore:line
44 44
     SETTINGS_SECTIONS: ['language', 'devices', 'moderator'],
45
+    INVITE_OPTIONS: ['invite', 'dialout', 'addtocall'],
45 46
     // Determines how the video would fit the screen. 'both' would fit the whole
46 47
     // screen, 'height' would fit the original video height to the height of the
47 48
     // screen, 'width' would fit the original video width to the width of the
@@ -124,4 +125,9 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
124 125
      * @type {number}
125 126
      */
126 127
     CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT: 5000
128
+
129
+    /**
130
+     * The name of the application connected to the "Add people" search service.
131
+     */
132
+    // ADD_PEOPLE_APP_NAME: ""
127 133
 };

+ 2
- 1
lang/main.json ファイルの表示

@@ -446,6 +446,7 @@
446 446
         "hidePassword": "Hide password",
447 447
         "inviteTo": "Invite people to __conferenceName__",
448 448
         "invitedYouTo": "__userName__ has invited you to the __inviteURL__ conference",
449
+        "invitePeople": "Invite people",
449 450
         "locked": "This call is locked. New callers must have the link and enter the password to join.",
450 451
         "showPassword": "Show password",
451 452
         "unlocked": "This call is unlocked. Any new caller with the link may join the call."
@@ -467,7 +468,7 @@
467 468
     },
468 469
     "dialOut": {
469 470
         "dial": "Dial",
470
-        "dialOut": "Call a phone number",
471
+        "dialOut": "Call a #",
471 472
         "statusMessage": "is now __status__",
472 473
         "enterPhone": "Enter phone number",
473 474
         "phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!"

+ 0
- 2
modules/UI/UI.js ファイルの表示

@@ -37,7 +37,6 @@ import {
37 37
     showDialPadButton,
38 38
     showEtherpadButton,
39 39
     showSharedVideoButton,
40
-    showDialOutButton,
41 40
     showToolbox
42 41
 } from '../../react/features/toolbox';
43 42
 import {
@@ -474,7 +473,6 @@ UI.onPeerVideoTypeChanged
474 473
 UI.updateLocalRole = isModerator => {
475 474
     VideoLayout.showModeratorIndicator();
476 475
 
477
-    APP.store.dispatch(showDialOutButton(isModerator));
478 476
     APP.store.dispatch(showSharedVideoButton());
479 477
 
480 478
     Recording.showRecordingButton(isModerator);

+ 6
- 13
modules/UI/videolayout/Filmstrip.js ファイルの表示

@@ -193,16 +193,6 @@ const Filmstrip = {
193 193
         }
194 194
     },
195 195
 
196
-    /**
197
-     * Returns the width of filmstip
198
-     * @returns {number} width
199
-     */
200
-    getFilmstripWidth() {
201
-        return this.filmstrip.innerWidth()
202
-            - parseInt(this.filmstrip.css('paddingLeft'), 10)
203
-            - parseInt(this.filmstrip.css('paddingRight'), 10);
204
-    },
205
-
206 196
     /**
207 197
      * Calculates the size for thumbnails: local and remote one
208 198
      * @returns {*|{localVideo, remoteVideo}}
@@ -433,11 +423,14 @@ const Filmstrip = {
433 423
             promises.push(new Promise((resolve) => {
434 424
                 // Let CSS take care of height in vertical filmstrip mode.
435 425
                 if (interfaceConfig.VERTICAL_FILMSTRIP) {
436
-                    resolve();
426
+                    $('#filmstripLocalVideo').animate({
427
+                        // adds 4 px because of small video 2px border
428
+                        width: local.thumbWidth + 4
429
+                    }, this._getAnimateOptions(animate, resolve));
437 430
                 } else {
438 431
                     this.filmstrip.animate({
439
-                        // adds 2 px because of small video 1px border
440
-                        height: remote.thumbHeight + 2
432
+                        // adds 4 px because of small video 2px border
433
+                        height: remote.thumbHeight + 4
441 434
                     }, this._getAnimateOptions(animate, resolve));
442 435
                 }
443 436
             }));

+ 1
- 1
modules/UI/videolayout/LocalVideo.js ファイルの表示

@@ -29,7 +29,7 @@ function LocalVideo(VideoLayout, emitter) {
29 29
     this.isLocal = true;
30 30
     this.emitter = emitter;
31 31
     this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
32
-        ? 'left bottom' : 'top center';
32
+        ? 'left top' : 'top center';
33 33
 
34 34
     Object.defineProperty(this, 'id', {
35 35
         get: function () {

+ 1
- 1
modules/UI/videolayout/RemoteVideo.js ファイルの表示

@@ -43,7 +43,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
43 43
     this.hasRemoteVideoMenu = false;
44 44
     this._supportsRemoteControl = false;
45 45
     this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
46
-        ? 'left top' : 'top center';
46
+        ? 'left bottom' : 'top center';
47 47
     this.addRemoteVideoContainer();
48 48
     this.updateIndicators();
49 49
     this.setDisplayName();

+ 11
- 0
react/features/base/conference/actionTypes.js ファイルの表示

@@ -151,3 +151,14 @@ export const SET_RECEIVE_VIDEO_QUALITY = Symbol('SET_RECEIVE_VIDEO_QUALITY');
151 151
  * }
152 152
  */
153 153
 export const SET_ROOM = Symbol('SET_ROOM');
154
+
155
+/**
156
+ * The type of (redux) action, which indicates if a SIP gateway is enabled on
157
+ * the server.
158
+ *
159
+ * {
160
+ *     type: SET_SIP_GATEWAY_ENABLED
161
+ *     isSIPGatewayEnabled: boolean
162
+ * }
163
+ */
164
+export const SET_SIP_GATEWAY_ENABLED = Symbol('SET_SIP_GATEWAY_ENABLED');

+ 19
- 1
react/features/base/conference/reducer.js ファイルの表示

@@ -14,7 +14,8 @@ import {
14 14
     SET_AUDIO_ONLY,
15 15
     SET_PASSWORD,
16 16
     SET_RECEIVE_VIDEO_QUALITY,
17
-    SET_ROOM
17
+    SET_ROOM,
18
+    SET_SIP_GATEWAY_ENABLED
18 19
 } from './actionTypes';
19 20
 import { VIDEO_QUALITY_LEVELS } from './constants';
20 21
 import { isRoomValid } from './functions';
@@ -60,6 +61,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
60 61
 
61 62
     case SET_ROOM:
62 63
         return _setRoom(state, action);
64
+
65
+    case SET_SIP_GATEWAY_ENABLED:
66
+        return _setSIPGatewayEnabled(state, action);
63 67
     }
64 68
 
65 69
     return state;
@@ -363,3 +367,17 @@ function _setRoom(state, action) {
363 367
      */
364 368
     return set(state, 'room', room);
365 369
 }
370
+
371
+/**
372
+ * Reduces a specific Redux action SET_SIP_GATEWAY_ENABLED of the feature
373
+ * base/conference.
374
+ *
375
+ * @param {Object} state - The Redux state of the feature base/conference.
376
+ * @param {Action} action - The Redux action SET_SIP_GATEWAY_ENABLED to reduce.
377
+ * @private
378
+ * @returns {Object} The new state of the feature base/conference after the
379
+ * reduction of the specified action.
380
+ */
381
+function _setSIPGatewayEnabled(state, action) {
382
+    return set(state, 'isSIPGatewayEnabled', action.isSIPGatewayEnabled);
383
+}

+ 1
- 1
react/features/conference/components/Conference.web.js ファイルの表示

@@ -74,7 +74,7 @@ class Conference extends Component {
74 74
             <div id = 'videoconference_page'>
75 75
                 <div id = 'videospace'>
76 76
                     <LargeVideo />
77
-                    <Filmstrip displayToolbox = { filmStripOnly } />
77
+                    <Filmstrip filmstripOnly = { filmStripOnly } />
78 78
                 </div>
79 79
 
80 80
                 { filmStripOnly ? null : <Toolbox /> }

+ 0
- 14
react/features/dial-out/actions.js ファイルの表示

@@ -1,5 +1,3 @@
1
-import { openDialog } from '../../features/base/dialog';
2
-
3 1
 import {
4 2
     DIAL_OUT_CANCELED,
5 3
     DIAL_OUT_CODES_UPDATED,
@@ -7,8 +5,6 @@ import {
7 5
     PHONE_NUMBER_CHECKED
8 6
 } from './actionTypes';
9 7
 
10
-import { DialOutDialog } from './components';
11
-
12 8
 declare var $: Function;
13 9
 declare var config: Object;
14 10
 
@@ -76,16 +72,6 @@ export function checkDialNumber(dialNumber) {
76 72
     };
77 73
 }
78 74
 
79
-
80
-/**
81
- * Opens the dial-out dialog.
82
- *
83
- * @returns {Function}
84
- */
85
-export function openDialOutDialog() {
86
-    return openDialog(DialOutDialog);
87
-}
88
-
89 75
 /**
90 76
  * Sends an ajax request for dial-out country codes.
91 77
  *

+ 7
- 4
react/features/filmstrip/components/Filmstrip.web.js ファイルの表示

@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
5 5
 import React, { Component } from 'react';
6 6
 import { connect } from 'react-redux';
7 7
 
8
+import { InviteButton } from '../../invite';
8 9
 import { Toolbox } from '../../toolbox';
9 10
 
10 11
 import { setFilmstripHovered } from '../actions';
@@ -48,9 +49,9 @@ class Filmstrip extends Component {
48 49
         dispatch: PropTypes.func,
49 50
 
50 51
         /**
51
-         * Whether or not the toolbox should be displayed within the filmstrip.
52
+         * Whether or not the conference is in filmstripOnly mode.
52 53
          */
53
-        displayToolbox: PropTypes.bool
54
+        filmstripOnly: PropTypes.bool
54 55
     };
55 56
 
56 57
     /**
@@ -100,7 +101,7 @@ class Filmstrip extends Component {
100 101
 
101 102
         return (
102 103
             <div className = { filmstripClassNames }>
103
-                { this.props.displayToolbox ? <Toolbox /> : null }
104
+                { this.props.filmstripOnly ? <Toolbox /> : null }
104 105
                 <div
105 106
                     className = 'filmstrip__videos'
106 107
                     id = 'remoteVideos'>
@@ -108,7 +109,9 @@ class Filmstrip extends Component {
108 109
                         className = 'filmstrip__videos'
109 110
                         id = 'filmstripLocalVideo'
110 111
                         onMouseOut = { this._onMouseOut }
111
-                        onMouseOver = { this._onMouseOver } />
112
+                        onMouseOver = { this._onMouseOver }>
113
+                        { this.props.filmstripOnly ? null : <InviteButton /> }
114
+                    </div>
112 115
                     <div
113 116
                         className = 'filmstrip__videos'
114 117
                         id = 'filmstripRemoteVideos'>

+ 1
- 10
react/features/invite/actions.js ファイルの表示

@@ -5,7 +5,7 @@ import {
5 5
     UPDATE_DIAL_IN_NUMBERS_FAILED,
6 6
     UPDATE_DIAL_IN_NUMBERS_SUCCESS
7 7
 } from './actionTypes';
8
-import { AddPeopleDialog, InviteDialog } from './components';
8
+import { InviteDialog } from './components';
9 9
 
10 10
 declare var $: Function;
11 11
 
@@ -18,15 +18,6 @@ export function openInviteDialog() {
18 18
     return openDialog(InviteDialog);
19 19
 }
20 20
 
21
-/**
22
- * Opens the Add People Dialog.
23
- *
24
- * @returns {Function}
25
- */
26
-export function openAddPeopleDialog() {
27
-    return openDialog(AddPeopleDialog);
28
-}
29
-
30 21
 /**
31 22
  * Opens the inline conference info dialog.
32 23
  *

+ 1
- 0
react/features/invite/components/InviteButton.native.js ファイルの表示

@@ -0,0 +1 @@
1
+

+ 231
- 0
react/features/invite/components/InviteButton.web.js ファイルの表示

@@ -0,0 +1,231 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+import Button from '@atlaskit/button';
5
+import DropdownMenu from '@atlaskit/dropdown-menu';
6
+
7
+import { translate } from '../../base/i18n';
8
+import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
9
+
10
+import { openDialog } from '../../base/dialog';
11
+import { AddPeopleDialog, InviteDialog } from '.';
12
+import { DialOutDialog } from '../../dial-out';
13
+import { isInviteOptionEnabled, getInviteOptionPosition } from '../functions';
14
+
15
+declare var interfaceConfig: Object;
16
+
17
+const SHARE_LINK_OPTION = 'invite';
18
+const DIAL_OUT_OPTION = 'dialout';
19
+const ADD_TO_CALL_OPTION = 'addtocall';
20
+
21
+/**
22
+ * The button that provides different invite options.
23
+ */
24
+class InviteButton extends Component {
25
+    /**
26
+     * {@code InviteButton}'s property types.
27
+     *
28
+     * @static
29
+     */
30
+    static propTypes = {
31
+        /**
32
+         * Indicates if the "Add to call" feature is available.
33
+         */
34
+        _isAddToCallAvailable: PropTypes.bool,
35
+
36
+        /**
37
+         * Indicates if the "Dial out" feature is available.
38
+         */
39
+        _isDialOutAvailable: PropTypes.bool,
40
+
41
+        /**
42
+         * The function opening the dialog.
43
+         */
44
+        openDialog: PropTypes.func,
45
+
46
+        /**
47
+         * Invoked to obtain translated strings.
48
+         */
49
+        t: PropTypes.func
50
+    };
51
+
52
+    /**
53
+     * Initializes a new {@code InviteButton} instance.
54
+     *
55
+     * @param {Object} props - The read-only properties with which the new
56
+     * instance is to be initialized.
57
+     */
58
+    constructor(props) {
59
+        super(props);
60
+
61
+        this._onInviteClick = this._onInviteClick.bind(this);
62
+        this._onInviteOptionSelected = this._onInviteOptionSelected.bind(this);
63
+        this._updateInviteItems = this._updateInviteItems.bind(this);
64
+
65
+        this._updateInviteItems(this.props);
66
+    }
67
+
68
+    /**
69
+     * Implements React's {@link Component#componentWillReceiveProps()}.
70
+     *
71
+     * @inheritdoc
72
+     * @param {Object} nextProps - The read-only props which this Component will
73
+     * receive.
74
+     * @returns {void}
75
+     */
76
+    componentWillReceiveProps(nextProps) {
77
+        if (this.props._isDialOutAvailable !== nextProps._isDialOutAvailable
78
+            || this.props._isAddToCallAvailable
79
+                !== nextProps._isAddToCallAvailable) {
80
+            this._updateInviteItems(nextProps);
81
+        }
82
+    }
83
+
84
+    /**
85
+     * Renders the content of this component.
86
+     *
87
+     * @returns {ReactElement}
88
+     */
89
+    render() {
90
+        const { t } = this.props;
91
+
92
+        const { VERTICAL_FILMSTRIP } = interfaceConfig;
93
+
94
+        return (
95
+            <div className = 'filmstrip__invite'>
96
+                <div className = 'invite-button-group'>
97
+                    <Button
98
+                        onClick = { this._onInviteClick }
99
+                        shouldFitContainer = { true }>
100
+                        { t('invite.invitePeople') }
101
+                    </Button>
102
+                    { this.props._isDialOutAvailable
103
+                        || this.props._isAddToCallAvailable
104
+                        ? <DropdownMenu
105
+                            items = { this.state.inviteOptions }
106
+                            onItemActivated = { this._onInviteOptionSelected }
107
+                            position = { VERTICAL_FILMSTRIP
108
+                                ? 'bottom right'
109
+                                : 'top right' }
110
+                            shouldFlip = { true }
111
+                            triggerType = 'button' />
112
+                        : null }
113
+                </div>
114
+            </div>
115
+        );
116
+    }
117
+
118
+    /**
119
+     * Handles the click of the invite button.
120
+     *
121
+     * @private
122
+     * @returns {void}
123
+     */
124
+    _onInviteClick() {
125
+        this.props.openDialog(InviteDialog);
126
+    }
127
+
128
+    /**
129
+     * Handles selection of the invite options.
130
+     *
131
+     * @param { Object } option - The invite option that has been selected from
132
+     * the dropdown menu.
133
+     * @private
134
+     * @returns {void}
135
+     */
136
+    _onInviteOptionSelected(option) {
137
+        this.state.inviteOptions[0].items.forEach(item => {
138
+            if (item.content === option.item.content) {
139
+                item.action();
140
+            }
141
+        });
142
+    }
143
+
144
+    /**
145
+     * Updates the invite items list depending on the availability of the
146
+     * features.
147
+     *
148
+     * @param {Object} props - The read-only properties of the component.
149
+     * @private
150
+     * @returns {void}
151
+     */
152
+    _updateInviteItems(props) {
153
+        const { t } = this.props;
154
+
155
+        const inviteItems = [];
156
+
157
+        inviteItems.splice(
158
+            getInviteOptionPosition(SHARE_LINK_OPTION),
159
+            0,
160
+            {
161
+                content: t('toolbar.invite'),
162
+                action: () => this.props.openDialog(InviteDialog)
163
+            }
164
+        );
165
+
166
+        if (props._isDialOutAvailable) {
167
+            inviteItems.splice(
168
+                getInviteOptionPosition(DIAL_OUT_OPTION),
169
+                0,
170
+                {
171
+                    content: t('dialOut.dialOut'),
172
+                    action: () => this.props.openDialog(DialOutDialog)
173
+                }
174
+            );
175
+        }
176
+
177
+        if (props._isAddToCallAvailable) {
178
+            inviteItems.splice(
179
+                getInviteOptionPosition(ADD_TO_CALL_OPTION),
180
+                0,
181
+                {
182
+                    content: interfaceConfig.ADD_PEOPLE_APP_NAME,
183
+                    action: () => this.props.openDialog(AddPeopleDialog)
184
+                }
185
+            );
186
+        }
187
+
188
+        this.state = {
189
+            /**
190
+             * The list of invite options.
191
+             */
192
+            inviteOptions: [
193
+                {
194
+                    items: inviteItems
195
+                }
196
+            ]
197
+        };
198
+    }
199
+}
200
+
201
+/**
202
+ * Maps (parts of) the Redux state to the associated {@code InviteButton}'s
203
+ * props.
204
+ *
205
+ * @param {Object} state - The Redux state.
206
+ * @private
207
+ * @returns {{
208
+ *     _isAddToCallAvailable: boolean,
209
+ *     _isDialOutAvailable: boolean
210
+ * }}
211
+ */
212
+function _mapStateToProps(state) {
213
+    const { enableUserRolesBasedOnToken } = state['features/base/config'];
214
+
215
+    const { conference } = state['features/base/conference'];
216
+
217
+    const { isGuest } = state['features/jwt'];
218
+
219
+    return {
220
+        _isAddToCallAvailable: !isGuest
221
+            && isInviteOptionEnabled(ADD_TO_CALL_OPTION),
222
+        _isDialOutAvailable:
223
+            getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR
224
+            && conference && conference.isSIPCallingSupported()
225
+            && isInviteOptionEnabled(DIAL_OUT_OPTION)
226
+            && (!enableUserRolesBasedOnToken || !isGuest)
227
+    };
228
+}
229
+
230
+export default translate(connect(
231
+    _mapStateToProps, { openDialog })(InviteButton));

+ 1
- 0
react/features/invite/components/index.js ファイルの表示

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

+ 25
- 0
react/features/invite/functions.js ファイルの表示

@@ -1,4 +1,5 @@
1 1
 declare var $: Function;
2
+declare var interfaceConfig: Object;
2 3
 
3 4
 /**
4 5
  * Sends an ajax request to a directory service.
@@ -76,3 +77,27 @@ export function inviteRooms(conference, rooms) {
76 77
         }
77 78
     }
78 79
 }
80
+
81
+/**
82
+ * Indicates if an invite option is enabled in the configuration.
83
+ *
84
+ * @param {string} name - The name of the option defined in
85
+ * interfaceConfig.INVITE_OPTIONS.
86
+ * @returns {boolean} - True to indicate that the given invite option is
87
+ * enabled, false - otherwise.
88
+ */
89
+export function isInviteOptionEnabled(name) {
90
+    return interfaceConfig.INVITE_OPTIONS.indexOf(name) !== -1;
91
+}
92
+
93
+/**
94
+ * Get the position of the invite option in the interfaceConfig.INVITE_OPTIONS
95
+ * list.
96
+ *
97
+ * @param {string} optionName - The invite option name.
98
+ * @private
99
+ * @returns {number} - The position of the option in the list.
100
+ */
101
+export function getInviteOptionPosition(optionName) {
102
+    return interfaceConfig.INVITE_OPTIONS.indexOf(optionName);
103
+}

+ 3
- 22
react/features/recording/components/RecordingLabel.web.js ファイルの表示

@@ -3,7 +3,6 @@ import React, { Component } from 'react';
3 3
 import { connect } from 'react-redux';
4 4
 
5 5
 import { translate } from '../../base/i18n';
6
-import { shouldRemoteVideosBeVisible } from '../../filmstrip';
7 6
 
8 7
 /**
9 8
  * Implements a React {@link Component} which displays the current state of
@@ -38,14 +37,6 @@ class RecordingLabel extends Component {
38 37
          */
39 38
         _labelDisplayConfiguration: PropTypes.object,
40 39
 
41
-        /**
42
-         * Whether or not remote videos within the filmstrip are currently
43
-         * visible. Depending on the visibility state, coupled with filmstrip
44
-         * visibility, CSS classes will be set to allow for adjusting of
45
-         * {@code RecordingLabel} positioning.
46
-         */
47
-        _remoteVideosVisible: PropTypes.bool,
48
-
49 40
         /**
50 41
          * Invoked to obtain translated string.
51 42
          */
@@ -106,9 +97,7 @@ class RecordingLabel extends Component {
106 97
             centered ? '' : 'moveToCorner',
107 98
             this.state.filmstripBecomingVisible ? 'opening' : '',
108 99
             this.props._filmstripVisible
109
-                ? 'with-filmstrip' : 'without-filmstrip',
110
-            this.props._remoteVideosVisible
111
-                ? 'with-remote-videos' : 'without-remote-videos'
100
+                ? 'with-filmstrip' : 'without-filmstrip'
112 101
         ].join(' ');
113 102
 
114 103
         return (
@@ -137,8 +126,7 @@ class RecordingLabel extends Component {
137 126
  * @private
138 127
  * @returns {{
139 128
  *     _filmstripVisible: boolean,
140
- *     _labelDisplayConfiguration: Object,
141
- *     _remoteVideosVisible: boolean,
129
+ *     _labelDisplayConfiguration: Object
142 130
  * }}
143 131
  */
144 132
 function _mapStateToProps(state) {
@@ -159,14 +147,7 @@ function _mapStateToProps(state) {
159 147
          *
160 148
          * @type {Object}
161 149
          */
162
-        _labelDisplayConfiguration: labelDisplayConfiguration,
163
-
164
-        /**
165
-         * Whether or not remote videos are displayed in the filmstrip.
166
-         *
167
-         * @type {boolean}
168
-         */
169
-        _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
150
+        _labelDisplayConfiguration: labelDisplayConfiguration
170 151
     };
171 152
 }
172 153
 

+ 1
- 1
react/features/remote-video-menu/components/RemoteVideoMenuTriggerButton.js ファイルの表示

@@ -107,7 +107,7 @@ class RemoteVideoMenuTriggerButton extends Component {
107 107
                 content = { content }
108 108
                 onPopoverOpen = { this._onShowRemoteMenu }
109 109
                 position = { interfaceConfig.VERTICAL_FILMSTRIP
110
-                    ? 'left middle' : 'top center' }>
110
+                    ? 'left bottom' : 'top center' }>
111 111
                 <span
112 112
                     className = 'popover-trigger remote-video-menu-trigger'>
113 113
                     <i

+ 0
- 23
react/features/toolbox/actions.web.js ファイルの表示

@@ -324,29 +324,6 @@ export function showSharedVideoButton(): Function {
324 324
     };
325 325
 }
326 326
 
327
-/**
328
- * Shows the dial out button if it's required and appropriate
329
- * flag is passed.
330
- *
331
- * @param {boolean} show - Flag showing whether to show button or not.
332
- * @returns {Function}
333
- */
334
-export function showDialOutButton(show: boolean): Function {
335
-    return (dispatch: Dispatch<*>, getState: Function) => {
336
-        const buttonName = 'dialout';
337
-
338
-        if (show
339
-                && APP.conference.sipGatewayEnabled()
340
-                && isButtonEnabled(buttonName)
341
-                && (!config.enableUserRolesBasedOnToken
342
-                    || !getState()['features/jwt'].isGuest)) {
343
-            dispatch(setToolbarButton(buttonName, {
344
-                hidden: false
345
-            }));
346
-        }
347
-    };
348
-}
349
-
350 327
 /**
351 328
  * Shows the toolbox for specified timeout.
352 329
  *

+ 366
- 384
react/features/toolbox/defaultToolbarButtons.web.js ファイルの表示

@@ -1,16 +1,16 @@
1 1
 /* @flow */
2 2
 
3 3
 import React from 'react';
4
+import _ from 'lodash';
4 5
 
5 6
 import { ParticipantCounter } from '../contact-list';
6 7
 import { openDeviceSelectionDialog } from '../device-selection';
7
-import { openDialOutDialog } from '../dial-out';
8 8
 
9 9
 import {
10 10
     InfoDialogButton,
11
-    openAddPeopleDialog,
12 11
     openInviteDialog
13 12
 } from '../invite';
13
+
14 14
 import { VideoQualityButton } from '../video-quality';
15 15
 
16 16
 import UIEvents from '../../../service/UI/UIEvents';
@@ -21,412 +21,394 @@ declare var APP: Object;
21 21
 declare var interfaceConfig: Object;
22 22
 declare var JitsiMeetJS: Object;
23 23
 
24
+let buttons: Object = {};
25
+
24 26
 /**
25
- * All toolbar buttons' descriptors.
27
+ * Returns a map of all button descriptors and according properties.
28
+ *
29
+ * @returns {*} - The maps of default button descriptors.
26 30
  */
27
-const buttons: Object = {
28
-    addtocall: {
29
-        classNames: [ 'button', 'icon-add' ],
30
-        enabled: true,
31
-        id: 'toolbar_button_add',
32
-        isDisplayed: () => !APP.store.getState()['features/jwt'].isGuest,
33
-        onClick(dispatch) {
34
-            JitsiMeetJS.analytics.sendEvent('toolbar.add.clicked');
35
-
36
-            dispatch(openAddPeopleDialog());
37
-        },
38
-        tooltipKey: 'toolbar.addPeople'
39
-    },
40
-
41
-    /**
42
-     * The descriptor of the camera toolbar button.
43
-     */
44
-    camera: {
45
-        classNames: [ 'button', 'icon-camera' ],
46
-        enabled: true,
47
-        isDisplayed: () => true,
48
-        id: 'toolbar_button_camera',
49
-        onClick() {
50
-            const newVideoMutedState = !APP.conference.isLocalVideoMuted();
51
-
52
-            if (newVideoMutedState) {
53
-                JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
54
-            } else {
55
-                JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
56
-            }
57
-            APP.UI.emitEvent(UIEvents.VIDEO_MUTED, newVideoMutedState);
58
-        },
59
-        popups: [
60
-            {
61
-                dataAttr: 'audioOnly.featureToggleDisabled',
62
-                dataInterpolate: { feature: 'video mute' },
63
-                id: 'unmuteWhileAudioOnly'
64
-            }
65
-        ],
66
-        shortcut: 'V',
67
-        shortcutAttr: 'toggleVideoPopover',
68
-        shortcutFunc() {
69
-            if (APP.conference.isAudioOnly()) {
70
-                APP.UI.emitEvent(UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY);
71
-
72
-                return;
73
-            }
74
-
75
-            JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
76
-            APP.conference.toggleVideoMuted();
77
-        },
78
-        shortcutDescription: 'keyboardShortcuts.videoMute',
79
-        tooltipKey: 'toolbar.videomute'
80
-    },
81
-
82
-    /**
83
-     * The descriptor of the chat toolbar button.
84
-     */
85
-    chat: {
86
-        classNames: [ 'button', 'icon-chat' ],
87
-        enabled: true,
88
-        html: <span className = 'badge-round'>
89
-            <span id = 'unreadMessages' />
90
-        </span>,
91
-        id: 'toolbar_button_chat',
92
-        onClick() {
93
-            JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
94
-            APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
95
-        },
96
-        shortcut: 'C',
97
-        shortcutAttr: 'toggleChatPopover',
98
-        shortcutFunc() {
99
-            JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
100
-            APP.UI.toggleChat();
101
-        },
102
-        shortcutDescription: 'keyboardShortcuts.toggleChat',
103
-        sideContainerId: 'chat_container',
104
-        tooltipKey: 'toolbar.chat'
105
-    },
106
-
107
-    /**
108
-     * The descriptor of the contact list toolbar button.
109
-     */
110
-    contacts: {
111
-        childComponent: ParticipantCounter,
112
-        classNames: [ 'button', 'icon-contactList' ],
113
-        enabled: true,
114
-        id: 'toolbar_contact_list',
115
-        onClick() {
116
-            JitsiMeetJS.analytics.sendEvent(
117
-                'toolbar.contacts.toggled');
118
-            APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
119
-        },
120
-        sideContainerId: 'contacts_container',
121
-        tooltipKey: 'bottomtoolbar.contactlist'
122
-    },
123
-
124
-    /**
125
-     * The descriptor of the desktop sharing toolbar button.
126
-     */
127
-    desktop: {
128
-        classNames: [ 'button', 'icon-share-desktop' ],
129
-        enabled: true,
130
-        id: 'toolbar_button_desktopsharing',
131
-        onClick() {
132
-            if (APP.conference.isSharingScreen) {
133
-                JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
134
-            } else {
135
-                JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
136
-            }
137
-            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
31
+function getDefaultButtons() {
32
+    if (!_.isEmpty(buttons)) {
33
+        return buttons;
34
+    }
35
+
36
+    buttons = {
37
+        /**
38
+         * The descriptor of the camera toolbar button.
39
+         */
40
+        camera: {
41
+            classNames: [ 'button', 'icon-camera' ],
42
+            enabled: true,
43
+            isDisplayed: () => true,
44
+            id: 'toolbar_button_camera',
45
+            onClick() {
46
+                const newVideoMutedState = !APP.conference.isLocalVideoMuted();
47
+
48
+                if (newVideoMutedState) {
49
+                    JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
50
+                } else {
51
+                    JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
52
+                }
53
+                APP.UI.emitEvent(UIEvents.VIDEO_MUTED, newVideoMutedState);
54
+            },
55
+            popups: [
56
+                {
57
+                    dataAttr: 'audioOnly.featureToggleDisabled',
58
+                    dataInterpolate: { feature: 'video mute' },
59
+                    id: 'unmuteWhileAudioOnly'
60
+                }
61
+            ],
62
+            shortcut: 'V',
63
+            shortcutAttr: 'toggleVideoPopover',
64
+            shortcutFunc() {
65
+                if (APP.conference.isAudioOnly()) {
66
+                    APP.UI.emitEvent(UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY);
67
+
68
+                    return;
69
+                }
70
+
71
+                JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
72
+                APP.conference.toggleVideoMuted();
73
+            },
74
+            shortcutDescription: 'keyboardShortcuts.videoMute',
75
+            tooltipKey: 'toolbar.videomute'
138 76
         },
139
-        popups: [
140
-            {
141
-                dataAttr: 'audioOnly.featureToggleDisabled',
142
-                dataInterpolate: { feature: 'screen sharing' },
143
-                id: 'screenshareWhileAudioOnly'
144
-            }
145
-        ],
146
-        shortcut: 'D',
147
-        shortcutAttr: 'toggleDesktopSharingPopover',
148
-        shortcutFunc() {
149
-            JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
150
-
151
-            // eslint-disable-next-line no-empty-function
152
-            APP.conference.toggleScreenSharing().catch(() => {});
77
+
78
+        /**
79
+         * The descriptor of the chat toolbar button.
80
+         */
81
+        chat: {
82
+            classNames: [ 'button', 'icon-chat' ],
83
+            enabled: true,
84
+            html: <span className = 'badge-round'>
85
+                <span id = 'unreadMessages' /></span>,
86
+            id: 'toolbar_button_chat',
87
+            onClick() {
88
+                JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
89
+                APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
90
+            },
91
+            shortcut: 'C',
92
+            shortcutAttr: 'toggleChatPopover',
93
+            shortcutFunc() {
94
+                JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
95
+                APP.UI.toggleChat();
96
+            },
97
+            shortcutDescription: 'keyboardShortcuts.toggleChat',
98
+            sideContainerId: 'chat_container',
99
+            tooltipKey: 'toolbar.chat'
153 100
         },
154
-        shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
155
-        tooltipKey: 'toolbar.sharescreen'
156
-    },
157
-
158
-    /**
159
-     * The descriptor of the dial out toolbar button.
160
-     */
161
-    dialout: {
162
-        classNames: [ 'button', 'icon-telephone' ],
163
-        enabled: true,
164
-
165
-        // Will be displayed once the SIP calls functionality is detected.
166
-        hidden: true,
167
-        id: 'toolbar_button_dial_out',
168
-        onClick(dispatch) {
169
-            JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
170
-
171
-            dispatch(openDialOutDialog());
101
+
102
+        /**
103
+         * The descriptor of the contact list toolbar button.
104
+         */
105
+        contacts: {
106
+            childComponent: ParticipantCounter,
107
+            classNames: [ 'button', 'icon-contactList' ],
108
+            enabled: true,
109
+            id: 'toolbar_contact_list',
110
+            onClick() {
111
+                JitsiMeetJS.analytics.sendEvent(
112
+                    'toolbar.contacts.toggled');
113
+                APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
114
+            },
115
+            sideContainerId: 'contacts_container',
116
+            tooltipKey: 'bottomtoolbar.contactlist'
172 117
         },
173
-        tooltipKey: 'dialOut.dialOut'
174
-    },
175
-
176
-    /**
177
-     * The descriptor of the device selection toolbar button.
178
-     */
179
-    fodeviceselection: {
180
-        classNames: [ 'button', 'icon-settings' ],
181
-        enabled: true,
182
-        isDisplayed() {
183
-            return interfaceConfig.filmStripOnly;
118
+
119
+        /**
120
+         * The descriptor of the desktop sharing toolbar button.
121
+         */
122
+        desktop: {
123
+            classNames: [ 'button', 'icon-share-desktop' ],
124
+            enabled: true,
125
+            id: 'toolbar_button_desktopsharing',
126
+            onClick() {
127
+                if (APP.conference.isSharingScreen) {
128
+                    JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
129
+                } else {
130
+                    JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
131
+                }
132
+                APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
133
+            },
134
+            popups: [
135
+                {
136
+                    dataAttr: 'audioOnly.featureToggleDisabled',
137
+                    dataInterpolate: { feature: 'screen sharing' },
138
+                    id: 'screenshareWhileAudioOnly'
139
+                }
140
+            ],
141
+            shortcut: 'D',
142
+            shortcutAttr: 'toggleDesktopSharingPopover',
143
+            shortcutFunc() {
144
+                JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
145
+
146
+                // eslint-disable-next-line no-empty-function
147
+                APP.conference.toggleScreenSharing().catch(() => {});
148
+            },
149
+            shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
150
+            tooltipKey: 'toolbar.sharescreen'
184 151
         },
185
-        id: 'toolbar_button_fodeviceselection',
186
-        onClick(dispatch) {
187
-            JitsiMeetJS.analytics.sendEvent(
188
-                'toolbar.fodeviceselection.toggled');
189 152
 
190
-            dispatch(openDeviceSelectionDialog());
153
+        /**
154
+         * The descriptor of the device selection toolbar button.
155
+         */
156
+        fodeviceselection: {
157
+            classNames: [ 'button', 'icon-settings' ],
158
+            enabled: true,
159
+            isDisplayed() {
160
+                return interfaceConfig.filmStripOnly;
161
+            },
162
+            id: 'toolbar_button_fodeviceselection',
163
+            onClick(dispatch: Function) {
164
+                JitsiMeetJS.analytics.sendEvent(
165
+                    'toolbar.fodeviceselection.toggled');
166
+
167
+                dispatch(openDeviceSelectionDialog());
168
+            },
169
+            sideContainerId: 'settings_container',
170
+            tooltipKey: 'toolbar.Settings'
191 171
         },
192
-        sideContainerId: 'settings_container',
193
-        tooltipKey: 'toolbar.Settings'
194
-    },
195
-
196
-    /**
197
-     * The descriptor of the dialpad toolbar button.
198
-     */
199
-    dialpad: {
200
-        classNames: [ 'button', 'icon-dialpad' ],
201
-        enabled: true,
202
-
203
-        // TODO: remove it after UI.updateDTMFSupport fix
204
-        hidden: true,
205
-        id: 'toolbar_button_dialpad',
206
-        onClick() {
207
-            JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
172
+
173
+        /**
174
+         * The descriptor of the dialpad toolbar button.
175
+         */
176
+        dialpad: {
177
+            classNames: [ 'button', 'icon-dialpad' ],
178
+            enabled: true,
179
+
180
+            // TODO: remove it after UI.updateDTMFSupport fix
181
+            hidden: true,
182
+            id: 'toolbar_button_dialpad',
183
+            onClick() {
184
+                JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
185
+            },
186
+            tooltipKey: 'toolbar.dialpad'
208 187
         },
209
-        tooltipKey: 'toolbar.dialpad'
210
-    },
211
-
212
-    /**
213
-     * The descriptor of the etherpad toolbar button.
214
-     */
215
-    etherpad: {
216
-        classNames: [ 'button', 'icon-share-doc' ],
217
-        enabled: true,
218
-        hidden: true,
219
-        id: 'toolbar_button_etherpad',
220
-        onClick() {
221
-            JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
222
-            APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
188
+
189
+        /**
190
+         * The descriptor of the etherpad toolbar button.
191
+         */
192
+        etherpad: {
193
+            classNames: [ 'button', 'icon-share-doc' ],
194
+            enabled: true,
195
+            hidden: true,
196
+            id: 'toolbar_button_etherpad',
197
+            onClick() {
198
+                JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
199
+                APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
200
+            },
201
+            tooltipKey: 'toolbar.etherpad'
223 202
         },
224
-        tooltipKey: 'toolbar.etherpad'
225
-    },
226
-
227
-    /**
228
-     * The descriptor of the toolbar button which toggles full-screen mode.
229
-     */
230
-    fullscreen: {
231
-        classNames: [ 'button', 'icon-full-screen' ],
232
-        enabled: true,
233
-        id: 'toolbar_button_fullScreen',
234
-        onClick() {
235
-            JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
236
-
237
-            APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
203
+
204
+        /**
205
+         * The descriptor of the toolbar button which toggles full-screen mode.
206
+         */
207
+        fullscreen: {
208
+            classNames: [ 'button', 'icon-full-screen' ],
209
+            enabled: true,
210
+            id: 'toolbar_button_fullScreen',
211
+            onClick() {
212
+                JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
213
+
214
+                APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
215
+            },
216
+            shortcut: 'S',
217
+            shortcutAttr: 'toggleFullscreenPopover',
218
+            shortcutDescription: 'keyboardShortcuts.fullScreen',
219
+            shortcutFunc() {
220
+                JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
221
+                APP.UI.toggleFullScreen();
222
+            },
223
+            tooltipKey: 'toolbar.fullscreen'
238 224
         },
239
-        shortcut: 'S',
240
-        shortcutAttr: 'toggleFullscreenPopover',
241
-        shortcutDescription: 'keyboardShortcuts.fullScreen',
242
-        shortcutFunc() {
243
-            JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
244
-            APP.UI.toggleFullScreen();
225
+
226
+        /**
227
+         * The descriptor of the toolbar button which hangs up the
228
+         * call/conference.
229
+         */
230
+        hangup: {
231
+            classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
232
+            enabled: true,
233
+            isDisplayed: () => true,
234
+            id: 'toolbar_button_hangup',
235
+            onClick() {
236
+                JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
237
+                APP.UI.emitEvent(UIEvents.HANGUP);
238
+            },
239
+            tooltipKey: 'toolbar.hangup'
245 240
         },
246
-        tooltipKey: 'toolbar.fullscreen'
247
-    },
248
-
249
-    /**
250
-     * The descriptor of the toolbar button which hangs up the call/conference.
251
-     */
252
-    hangup: {
253
-        classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
254
-        enabled: true,
255
-        isDisplayed: () => true,
256
-        id: 'toolbar_button_hangup',
257
-        onClick() {
258
-            JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
259
-            APP.UI.emitEvent(UIEvents.HANGUP);
241
+
242
+        /**
243
+         * The descriptor of the toolbar button which opens a dialog for the
244
+         * conference URL and inviting others.
245
+         */
246
+        info: {
247
+            component: InfoDialogButton
260 248
         },
261
-        tooltipKey: 'toolbar.hangup'
262
-    },
263
-
264
-    /**
265
-     * The descriptor of the toolbar button which opens a dialog for the
266
-     * conference URL and inviting others.
267
-     */
268
-    info: {
269
-        component: InfoDialogButton
270
-    },
271
-
272
-    /**
273
-     * The descriptor of the toolbar button which shows the invite user dialog.
274
-     */
275
-    invite: {
276
-        classNames: [ 'button', 'icon-link' ],
277
-        enabled: true,
278
-        id: 'toolbar_button_link',
279
-        onClick(dispatch) {
280
-            JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
281
-
282
-            dispatch(openInviteDialog());
249
+
250
+        /**
251
+         * The descriptor of the toolbar button which shows the invite user
252
+         * dialog.
253
+         */
254
+        invite: {
255
+            classNames: [ 'button', 'icon-link' ],
256
+            enabled: true,
257
+            id: 'toolbar_button_link',
258
+            onClick(dispatch: Function) {
259
+                JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
260
+
261
+                dispatch(openInviteDialog());
262
+            },
263
+            tooltipKey: 'toolbar.invite'
283 264
         },
284
-        tooltipKey: 'toolbar.invite'
285
-    },
286
-
287
-    /**
288
-     * The descriptor of the microphone toolbar button.
289
-     */
290
-    microphone: {
291
-        classNames: [ 'button', 'icon-microphone' ],
292
-        enabled: true,
293
-        isDisplayed: () => true,
294
-        id: 'toolbar_button_mute',
295
-        onClick() {
296
-            const sharedVideoManager = APP.UI.getSharedVideoManager();
297
-
298
-            if (APP.conference.isLocalAudioMuted()) {
299
-                // If there's a shared video with the volume "on" and we aren't
300
-                // the video owner, we warn the user
301
-                // that currently it's not possible to unmute.
302
-                if (sharedVideoManager
303
-                    && sharedVideoManager.isSharedVideoVolumeOn()
304
-                    && !sharedVideoManager.isSharedVideoOwner()) {
305
-                    APP.UI.showCustomToolbarPopup(
306
-                        '#unableToUnmutePopup', true, 5000);
265
+
266
+        /**
267
+         * The descriptor of the microphone toolbar button.
268
+         */
269
+        microphone: {
270
+            classNames: [ 'button', 'icon-microphone' ],
271
+            enabled: true,
272
+            isDisplayed: () => true,
273
+            id: 'toolbar_button_mute',
274
+            onClick() {
275
+                const sharedVideoManager = APP.UI.getSharedVideoManager();
276
+
277
+                if (APP.conference.isLocalAudioMuted()) {
278
+                    // If there's a shared video with the volume "on" and we
279
+                    // aren't the video owner, we warn the user
280
+                    // that currently it's not possible to unmute.
281
+                    if (sharedVideoManager
282
+                        && sharedVideoManager.isSharedVideoVolumeOn()
283
+                        && !sharedVideoManager.isSharedVideoOwner()) {
284
+                        APP.UI.showCustomToolbarPopup(
285
+                            '#unableToUnmutePopup', true, 5000);
286
+                    } else {
287
+                        JitsiMeetJS.analytics
288
+                            .sendEvent('toolbar.audio.unmuted');
289
+                        APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
290
+                    }
307 291
                 } else {
308
-                    JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
309
-                    APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
292
+                    JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
293
+                    APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
310 294
                 }
311
-            } else {
312
-                JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
313
-                APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
314
-            }
315
-        },
316
-        popups: [
317
-            {
318
-                dataAttr: 'toolbar.micMutedPopup',
319
-                id: 'micMutedPopup'
320 295
             },
321
-            {
322
-                dataAttr: 'toolbar.unableToUnmutePopup',
323
-                id: 'unableToUnmutePopup'
296
+            popups: [
297
+                {
298
+                    dataAttr: 'toolbar.micMutedPopup',
299
+                    id: 'micMutedPopup'
300
+                },
301
+                {
302
+                    dataAttr: 'toolbar.unableToUnmutePopup',
303
+                    id: 'unableToUnmutePopup'
304
+                },
305
+                {
306
+                    dataAttr: 'toolbar.talkWhileMutedPopup',
307
+                    id: 'talkWhileMutedPopup'
308
+                }
309
+            ],
310
+            shortcut: 'M',
311
+            shortcutAttr: 'mutePopover',
312
+            shortcutFunc() {
313
+                JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
314
+                APP.conference.toggleAudioMuted();
324 315
             },
325
-            {
326
-                dataAttr: 'toolbar.talkWhileMutedPopup',
327
-                id: 'talkWhileMutedPopup'
328
-            }
329
-        ],
330
-        shortcut: 'M',
331
-        shortcutAttr: 'mutePopover',
332
-        shortcutFunc() {
333
-            JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
334
-            APP.conference.toggleAudioMuted();
316
+            shortcutDescription: 'keyboardShortcuts.mute',
317
+            tooltipKey: 'toolbar.mute'
335 318
         },
336
-        shortcutDescription: 'keyboardShortcuts.mute',
337
-        tooltipKey: 'toolbar.mute'
338
-    },
339
-
340
-    /**
341
-     * The descriptor of the profile toolbar button.
342
-     */
343
-    profile: {
344
-        component: ProfileButton,
345
-        sideContainerId: 'profile_container'
346
-    },
347
-
348
-    /**
349
-     * The descriptor of the "Raise hand" toolbar button.
350
-     */
351
-    raisehand: {
352
-        classNames: [ 'button', 'icon-raised-hand' ],
353
-        enabled: true,
354
-        id: 'toolbar_button_raisehand',
355
-        onClick() {
356
-            JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
357
-            APP.conference.maybeToggleRaisedHand();
319
+
320
+        /**
321
+         * The descriptor of the profile toolbar button.
322
+         */
323
+        profile: {
324
+            component: ProfileButton,
325
+            sideContainerId: 'profile_container'
358 326
         },
359
-        shortcut: 'R',
360
-        shortcutAttr: 'raiseHandPopover',
361
-        shortcutDescription: 'keyboardShortcuts.raiseHand',
362
-        shortcutFunc() {
363
-            JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked');
364
-            APP.conference.maybeToggleRaisedHand();
327
+
328
+        /**
329
+         * The descriptor of the "Raise hand" toolbar button.
330
+         */
331
+        raisehand: {
332
+            classNames: [ 'button', 'icon-raised-hand' ],
333
+            enabled: true,
334
+            id: 'toolbar_button_raisehand',
335
+            onClick() {
336
+                JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
337
+                APP.conference.maybeToggleRaisedHand();
338
+            },
339
+            shortcut: 'R',
340
+            shortcutAttr: 'raiseHandPopover',
341
+            shortcutDescription: 'keyboardShortcuts.raiseHand',
342
+            shortcutFunc() {
343
+                JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked');
344
+                APP.conference.maybeToggleRaisedHand();
345
+            },
346
+            tooltipKey: 'toolbar.raiseHand'
365 347
         },
366
-        tooltipKey: 'toolbar.raiseHand'
367
-    },
368
-
369
-    /**
370
-     * The descriptor of the recording toolbar button. Requires additional
371
-     * initialization in the recording module.
372
-     */
373
-    recording: {
374
-        classNames: [ 'button' ],
375
-        enabled: true,
376
-
377
-        // will be displayed once the recording functionality is detected
378
-        hidden: true,
379
-        id: 'toolbar_button_record',
380
-        tooltipKey: 'liveStreaming.buttonTooltip'
381
-    },
382
-
383
-    /**
384
-     * The descriptor of the settings toolbar button.
385
-     */
386
-    settings: {
387
-        classNames: [ 'button', 'icon-settings' ],
388
-        enabled: true,
389
-        id: 'toolbar_button_settings',
390
-        onClick() {
391
-            JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
392
-            APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
348
+
349
+        /**
350
+         * The descriptor of the recording toolbar button. Requires additional
351
+         * initialization in the recording module.
352
+         */
353
+        recording: {
354
+            classNames: [ 'button' ],
355
+            enabled: true,
356
+
357
+            // will be displayed once the recording functionality is detected
358
+            hidden: true,
359
+            id: 'toolbar_button_record',
360
+            tooltipKey: 'liveStreaming.buttonTooltip'
393 361
         },
394
-        sideContainerId: 'settings_container',
395
-        tooltipKey: 'toolbar.Settings'
396
-    },
397
-
398
-    /**
399
-     * The descriptor of the "Share YouTube video" toolbar button.
400
-     */
401
-    sharedvideo: {
402
-        classNames: [ 'button', 'icon-shared-video' ],
403
-        enabled: true,
404
-        id: 'toolbar_button_sharedvideo',
405
-        onClick() {
406
-            JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
407
-            APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
362
+
363
+        /**
364
+         * The descriptor of the settings toolbar button.
365
+         */
366
+        settings: {
367
+            classNames: [ 'button', 'icon-settings' ],
368
+            enabled: true,
369
+            id: 'toolbar_button_settings',
370
+            onClick() {
371
+                JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
372
+                APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
373
+            },
374
+            sideContainerId: 'settings_container',
375
+            tooltipKey: 'toolbar.Settings'
408 376
         },
409
-        popups: [
410
-            {
411
-                dataAttr: 'toolbar.sharedVideoMutedPopup',
412
-                id: 'sharedVideoMutedPopup'
413
-            }
414
-        ],
415
-        tooltipKey: 'toolbar.sharedvideo'
416
-    },
417
-
418
-    videoquality: {
419
-        component: VideoQualityButton
420
-    }
421
-};
422 377
 
378
+        /**
379
+         * The descriptor of the "Share YouTube video" toolbar button.
380
+         */
381
+        sharedvideo: {
382
+            classNames: [ 'button', 'icon-shared-video' ],
383
+            enabled: true,
384
+            id: 'toolbar_button_sharedvideo',
385
+            onClick() {
386
+                JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
387
+                APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
388
+            },
389
+            popups: [
390
+                {
391
+                    dataAttr: 'toolbar.sharedVideoMutedPopup',
392
+                    id: 'sharedVideoMutedPopup'
393
+                }
394
+            ],
395
+            tooltipKey: 'toolbar.sharedvideo'
396
+        },
423 397
 
424
-Object.keys(buttons).forEach(name => {
425
-    const button = buttons[name];
398
+        videoquality: {
399
+            component: VideoQualityButton
400
+        }
401
+    };
426 402
 
427
-    if (!button.isDisplayed) {
428
-        button.isDisplayed = () => !interfaceConfig.filmStripOnly;
429
-    }
430
-});
403
+    Object.keys(buttons).forEach(name => {
404
+        const button = buttons[name];
405
+
406
+        if (!button.isDisplayed) {
407
+            button.isDisplayed = () => !interfaceConfig.filmStripOnly;
408
+        }
409
+    });
410
+
411
+    return buttons;
412
+}
431 413
 
432
-export default buttons;
414
+export default getDefaultButtons;

+ 3
- 2
react/features/toolbox/functions.web.js ファイルの表示

@@ -1,7 +1,7 @@
1 1
 import SideContainerToggler
2 2
     from '../../../modules/UI/side_pannels/SideContainerToggler';
3 3
 
4
-import defaultToolbarButtons from './defaultToolbarButtons';
4
+import getDefaultButtons from './defaultToolbarButtons';
5 5
 
6 6
 declare var interfaceConfig: Object;
7 7
 
@@ -27,7 +27,8 @@ export function getDefaultToolboxButtons(buttonHandlers: Object): Object {
27 27
         toolbarButtons
28 28
             = interfaceConfig.TOOLBAR_BUTTONS.reduce(
29 29
                 (acc, buttonName) => {
30
-                    let button = defaultToolbarButtons[buttonName];
30
+                    const buttons = getDefaultButtons();
31
+                    let button = buttons ? buttons[buttonName] : null;
31 32
                     const currentButtonHandlers = buttonHandlers[buttonName];
32 33
 
33 34
                     if (button) {

+ 3
- 2
react/features/toolbox/reducer.js ファイルの表示

@@ -15,7 +15,7 @@ import {
15 15
     SET_TOOLBOX_TIMEOUT_MS,
16 16
     SET_TOOLBOX_VISIBLE
17 17
 } from './actionTypes';
18
-import defaultToolbarButtons from './defaultToolbarButtons';
18
+import getDefaultButtons from './defaultToolbarButtons';
19 19
 
20 20
 declare var interfaceConfig: Object;
21 21
 
@@ -209,7 +209,8 @@ ReducerRegistry.register(
209 209
  * @returns {Object}
210 210
  */
211 211
 function _setButton(state, { button, buttonName }): Object {
212
-    const buttonDefinition = defaultToolbarButtons[buttonName];
212
+    const buttons = getDefaultButtons();
213
+    const buttonDefinition = buttons ? buttons[buttonName] : null;
213 214
 
214 215
     // We don't need to update if the button shouldn't be displayed
215 216
     if (!buttonDefinition || !buttonDefinition.isDisplayed()) {

+ 1
- 14
react/features/video-quality/components/VideoQualityLabel.web.js ファイルの表示

@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
5 5
 
6 6
 import { VIDEO_QUALITY_LEVELS } from '../../base/conference';
7 7
 import { translate } from '../../base/i18n';
8
-import { shouldRemoteVideosBeVisible } from '../../filmstrip';
9 8
 
10 9
 const { HIGH, STANDARD, LOW } = VIDEO_QUALITY_LEVELS;
11 10
 
@@ -59,12 +58,6 @@ export class VideoQualityLabel extends Component {
59 58
          */
60 59
         _filmstripVisible: PropTypes.bool,
61 60
 
62
-        /**
63
-         * Whether or note remote videos are visible in the filmstrip,
64
-         * regardless of count. Used to determine display classes to set.
65
-         */
66
-        _remoteVideosVisible: PropTypes.bool,
67
-
68 61
         /**
69 62
          * The current video resolution (height) to display a label for.
70 63
          */
@@ -123,7 +116,6 @@ export class VideoQualityLabel extends Component {
123 116
             _audioOnly,
124 117
             _conferenceStarted,
125 118
             _filmstripVisible,
126
-            _remoteVideosVisible,
127 119
             _resolution,
128 120
             t
129 121
         } = this.props;
@@ -140,12 +132,9 @@ export class VideoQualityLabel extends Component {
140 132
         const baseClasses = 'video-state-indicator moveToCorner';
141 133
         const filmstrip
142 134
             = _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
143
-        const remoteVideosVisible = _remoteVideosVisible
144
-            ? 'with-remote-videos'
145
-            : 'without-remote-videos';
146 135
         const opening = this.state.togglingToVisible ? 'opening' : '';
147 136
         const classNames
148
-            = `${baseClasses} ${filmstrip} ${remoteVideosVisible} ${opening}`;
137
+            = `${baseClasses} ${filmstrip} ${opening}`;
149 138
         const tooltipKey
150 139
             = `videoStatus.labelTooltip${_audioOnly ? 'AudioOnly' : 'Video'}`;
151 140
 
@@ -206,7 +195,6 @@ export class VideoQualityLabel extends Component {
206 195
  *     _audioOnly: boolean,
207 196
  *     _conferenceStarted: boolean,
208 197
  *     _filmstripVisible: true,
209
- *     _remoteVideosVisible: boolean,
210 198
  *     _resolution: number
211 199
  * }}
212 200
  */
@@ -219,7 +207,6 @@ function _mapStateToProps(state) {
219 207
         _audioOnly: audioOnly,
220 208
         _conferenceStarted: Boolean(conference),
221 209
         _filmstripVisible: visible,
222
-        _remoteVideosVisible: shouldRemoteVideosBeVisible(state),
223 210
         _resolution: resolution
224 211
     };
225 212
 }

読み込み中…
キャンセル
保存