|
@@ -16,6 +16,7 @@ import {
|
16
|
16
|
getParticipants,
|
17
|
17
|
participantUpdated
|
18
|
18
|
} from '../../../base/participants';
|
|
19
|
+import { OverflowMenuItem } from '../../../base/toolbox';
|
19
|
20
|
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
20
|
21
|
import { ChatCounter, toggleChat } from '../../../chat';
|
21
|
22
|
import { toggleDocument } from '../../../etherpad';
|
|
@@ -56,7 +57,6 @@ import {
|
56
|
57
|
import AudioMuteButton from '../AudioMuteButton';
|
57
|
58
|
import HangupButton from '../HangupButton';
|
58
|
59
|
import OverflowMenuButton from './OverflowMenuButton';
|
59
|
|
-import OverflowMenuItem from './OverflowMenuItem';
|
60
|
60
|
import OverflowMenuProfileItem from './OverflowMenuProfileItem';
|
61
|
61
|
import ToolbarButton from './ToolbarButton';
|
62
|
62
|
import VideoMuteButton from '../VideoMuteButton';
|
|
@@ -177,6 +177,17 @@ type Props = {
|
177
|
177
|
t: Function
|
178
|
178
|
};
|
179
|
179
|
|
|
180
|
+/**
|
|
181
|
+ * The type of the React {@code Component} state of {@link Toolbox}.
|
|
182
|
+ */
|
|
183
|
+type State = {
|
|
184
|
+
|
|
185
|
+ /**
|
|
186
|
+ * The width of the browser's window.
|
|
187
|
+ */
|
|
188
|
+ windowWidth: number
|
|
189
|
+};
|
|
190
|
+
|
180
|
191
|
declare var APP: Object;
|
181
|
192
|
declare var interfaceConfig: Object;
|
182
|
193
|
|
|
@@ -185,7 +196,7 @@ declare var interfaceConfig: Object;
|
185
|
196
|
*
|
186
|
197
|
* @extends Component
|
187
|
198
|
*/
|
188
|
|
-class Toolbox extends Component<Props> {
|
|
199
|
+class Toolbox extends Component<Props, State> {
|
189
|
200
|
/**
|
190
|
201
|
* Initializes a new {@code Toolbox} instance.
|
191
|
202
|
*
|
|
@@ -198,6 +209,7 @@ class Toolbox extends Component<Props> {
|
198
|
209
|
// Bind event handlers so they are only bound once per instance.
|
199
|
210
|
this._onMouseOut = this._onMouseOut.bind(this);
|
200
|
211
|
this._onMouseOver = this._onMouseOver.bind(this);
|
|
212
|
+ this._onResize = this._onResize.bind(this);
|
201
|
213
|
this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this);
|
202
|
214
|
|
203
|
215
|
this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this);
|
|
@@ -232,6 +244,10 @@ class Toolbox extends Component<Props> {
|
232
|
244
|
= this._onToolbarToggleSharedVideo.bind(this);
|
233
|
245
|
this._onToolbarOpenLocalRecordingInfoDialog
|
234
|
246
|
= this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
|
|
247
|
+
|
|
248
|
+ this.state = {
|
|
249
|
+ windowWidth: window.innerWidth
|
|
250
|
+ };
|
235
|
251
|
}
|
236
|
252
|
|
237
|
253
|
/**
|
|
@@ -273,6 +289,8 @@ class Toolbox extends Component<Props> {
|
273
|
289
|
shortcut.helpDescription);
|
274
|
290
|
}
|
275
|
291
|
});
|
|
292
|
+
|
|
293
|
+ window.addEventListener('resize', this._onResize);
|
276
|
294
|
}
|
277
|
295
|
|
278
|
296
|
/**
|
|
@@ -303,6 +321,8 @@ class Toolbox extends Component<Props> {
|
303
|
321
|
componentWillUnmount() {
|
304
|
322
|
[ 'C', 'D', 'R', 'S' ].forEach(letter =>
|
305
|
323
|
APP.keyboardshortcut.unregisterShortcut(letter));
|
|
324
|
+
|
|
325
|
+ window.removeEventListener('resize', this._onResize);
|
306
|
326
|
}
|
307
|
327
|
|
308
|
328
|
/**
|
|
@@ -482,6 +502,24 @@ class Toolbox extends Component<Props> {
|
482
|
502
|
this.props.dispatch(setToolbarHovered(true));
|
483
|
503
|
}
|
484
|
504
|
|
|
505
|
+ _onResize: () => void;
|
|
506
|
+
|
|
507
|
+ /**
|
|
508
|
+ * A window resize handler used to calculate the number of buttons we can
|
|
509
|
+ * fit in the toolbar.
|
|
510
|
+ *
|
|
511
|
+ * @private
|
|
512
|
+ * @returns {void}
|
|
513
|
+ */
|
|
514
|
+ _onResize() {
|
|
515
|
+ const width = window.innerWidth;
|
|
516
|
+
|
|
517
|
+ if (this.state.windowWidth !== width) {
|
|
518
|
+ this.setState({ windowWidth: width });
|
|
519
|
+ }
|
|
520
|
+ }
|
|
521
|
+
|
|
522
|
+
|
485
|
523
|
_onSetOverflowVisible: (boolean) => void;
|
486
|
524
|
|
487
|
525
|
/**
|
|
@@ -788,13 +826,30 @@ class Toolbox extends Component<Props> {
|
788
|
826
|
this.props.dispatch(openDialog(LocalRecordingInfoDialog));
|
789
|
827
|
}
|
790
|
828
|
|
|
829
|
+ /**
|
|
830
|
+ * Returns true if the the desktop sharing button should be visible and
|
|
831
|
+ * false otherwise.
|
|
832
|
+ *
|
|
833
|
+ * @returns {boolean}
|
|
834
|
+ */
|
|
835
|
+ _isDesktopSharingButtonVisible() {
|
|
836
|
+ const {
|
|
837
|
+ _desktopSharingEnabled,
|
|
838
|
+ _desktopSharingDisabledTooltipKey
|
|
839
|
+ } = this.props;
|
|
840
|
+
|
|
841
|
+ return _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
|
|
842
|
+ }
|
|
843
|
+
|
791
|
844
|
/**
|
792
|
845
|
* Renders a button for toggleing screen sharing.
|
793
|
846
|
*
|
794
|
847
|
* @private
|
|
848
|
+ * @param {boolean} isInOverflowMenu - True if the button is moved to the
|
|
849
|
+ * overflow menu.
|
795
|
850
|
* @returns {ReactElement|null}
|
796
|
851
|
*/
|
797
|
|
- _renderDesktopSharingButton() {
|
|
852
|
+ _renderDesktopSharingButton(isInOverflowMenu = false) {
|
798
|
853
|
const {
|
799
|
854
|
_desktopSharingEnabled,
|
800
|
855
|
_desktopSharingDisabledTooltipKey,
|
|
@@ -802,13 +857,28 @@ class Toolbox extends Component<Props> {
|
802
|
857
|
t
|
803
|
858
|
} = this.props;
|
804
|
859
|
|
805
|
|
- const visible
|
806
|
|
- = _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
|
807
|
|
-
|
808
|
|
- if (!visible) {
|
|
860
|
+ if (!this._isDesktopSharingButtonVisible()) {
|
809
|
861
|
return null;
|
810
|
862
|
}
|
811
|
863
|
|
|
864
|
+ if (isInOverflowMenu) {
|
|
865
|
+ return (
|
|
866
|
+ <OverflowMenuItem
|
|
867
|
+ accessibilityLabel
|
|
868
|
+ = { t('toolbar.accessibilityLabel.shareYourScreen') }
|
|
869
|
+ disabled = { _desktopSharingEnabled }
|
|
870
|
+ icon = { 'icon-share-desktop' }
|
|
871
|
+ key = 'desktop'
|
|
872
|
+ onClick = { this._onToolbarToggleScreenshare }
|
|
873
|
+ text = {
|
|
874
|
+ t(`toolbar.${
|
|
875
|
+ _screensharing
|
|
876
|
+ ? 'stopScreenSharing' : 'startScreenSharing'}`
|
|
877
|
+ )
|
|
878
|
+ } />
|
|
879
|
+ );
|
|
880
|
+ }
|
|
881
|
+
|
812
|
882
|
const classNames = `icon-share-desktop ${
|
813
|
883
|
_screensharing ? 'toggled' : ''} ${
|
814
|
884
|
_desktopSharingEnabled ? '' : 'disabled'}`;
|
|
@@ -826,6 +896,15 @@ class Toolbox extends Component<Props> {
|
826
|
896
|
);
|
827
|
897
|
}
|
828
|
898
|
|
|
899
|
+ /**
|
|
900
|
+ * Returns true if the profile button is visible and false otherwise.
|
|
901
|
+ *
|
|
902
|
+ * @returns {boolean}
|
|
903
|
+ */
|
|
904
|
+ _isProfileVisible() {
|
|
905
|
+ return this.props._isGuest && this._shouldShowButton('profile');
|
|
906
|
+ }
|
|
907
|
+
|
829
|
908
|
/**
|
830
|
909
|
* Renders the list elements of the overflow menu.
|
831
|
910
|
*
|
|
@@ -838,14 +917,12 @@ class Toolbox extends Component<Props> {
|
838
|
917
|
_etherpadInitialized,
|
839
|
918
|
_feedbackConfigured,
|
840
|
919
|
_fullScreen,
|
841
|
|
- _isGuest,
|
842
|
920
|
_sharingVideo,
|
843
|
921
|
t
|
844
|
922
|
} = this.props;
|
845
|
923
|
|
846
|
924
|
return [
|
847
|
|
- _isGuest
|
848
|
|
- && this._shouldShowButton('profile')
|
|
925
|
+ this._isProfileVisible()
|
849
|
926
|
&& <OverflowMenuProfileItem
|
850
|
927
|
key = 'profile'
|
851
|
928
|
onClick = { this._onToolbarToggleProfile } />,
|
|
@@ -924,6 +1001,88 @@ class Toolbox extends Component<Props> {
|
924
|
1001
|
];
|
925
|
1002
|
}
|
926
|
1003
|
|
|
1004
|
+ /**
|
|
1005
|
+ * Renders a list of buttons that are moved to the overflow menu.
|
|
1006
|
+ *
|
|
1007
|
+ * @private
|
|
1008
|
+ * @param {Array<string>} movedButtons - The names of the buttons to be
|
|
1009
|
+ * moved.
|
|
1010
|
+ * @returns {Array<ReactElement>}
|
|
1011
|
+ */
|
|
1012
|
+ _renderMovedButtons(movedButtons) {
|
|
1013
|
+ const {
|
|
1014
|
+ _chatOpen,
|
|
1015
|
+ _raisedHand,
|
|
1016
|
+ t
|
|
1017
|
+ } = this.props;
|
|
1018
|
+
|
|
1019
|
+ return movedButtons.map(buttonName => {
|
|
1020
|
+ switch (buttonName) {
|
|
1021
|
+ case 'desktop':
|
|
1022
|
+ return this._renderDesktopSharingButton(true);
|
|
1023
|
+ case 'raisehand':
|
|
1024
|
+ return (
|
|
1025
|
+ <OverflowMenuItem
|
|
1026
|
+ accessibilityLabel =
|
|
1027
|
+ { t('toolbar.accessibilityLabel.raiseHand') }
|
|
1028
|
+ icon = { 'icon-raised-hand' }
|
|
1029
|
+ key = 'raisedHand'
|
|
1030
|
+ onClick = { this._onToolbarToggleRaiseHand }
|
|
1031
|
+ text = {
|
|
1032
|
+ t(`toolbar.${
|
|
1033
|
+ _raisedHand
|
|
1034
|
+ ? 'lowerYourHand' : 'raiseYourHand'}`
|
|
1035
|
+ )
|
|
1036
|
+ } />
|
|
1037
|
+ );
|
|
1038
|
+ case 'chat':
|
|
1039
|
+ return (
|
|
1040
|
+ <OverflowMenuItem
|
|
1041
|
+ accessibilityLabel =
|
|
1042
|
+ { t('toolbar.accessibilityLabel.chat') }
|
|
1043
|
+ icon = { 'icon-chat' }
|
|
1044
|
+ key = 'chat'
|
|
1045
|
+ onClick = { this._onToolbarToggleChat }
|
|
1046
|
+ text = {
|
|
1047
|
+ t(`toolbar.${
|
|
1048
|
+ _chatOpen ? 'closeChat' : 'openChat'}`
|
|
1049
|
+ )
|
|
1050
|
+ } />
|
|
1051
|
+ );
|
|
1052
|
+ case 'closedcaptions':
|
|
1053
|
+ return <ClosedCaptionButton showLabel = { true } />;
|
|
1054
|
+ case 'info':
|
|
1055
|
+ return <InfoDialogButton showLabel = { true } />;
|
|
1056
|
+ case 'invite':
|
|
1057
|
+ return (
|
|
1058
|
+ <OverflowMenuItem
|
|
1059
|
+ accessibilityLabel =
|
|
1060
|
+ { t('toolbar.accessibilityLabel.invite') }
|
|
1061
|
+ icon = 'icon-invite'
|
|
1062
|
+ key = 'invite'
|
|
1063
|
+ onClick = { this._onToolbarOpenInvite }
|
|
1064
|
+ text = { t('toolbar.invite') } />
|
|
1065
|
+ );
|
|
1066
|
+ case 'tileview':
|
|
1067
|
+ return <TileViewButton showLabel = { true } />;
|
|
1068
|
+ case 'localrecording':
|
|
1069
|
+ return (
|
|
1070
|
+ <OverflowMenuItem
|
|
1071
|
+ accessibilityLabel
|
|
1072
|
+ = { t('toolbar.accessibilityLabel.localRecording') }
|
|
1073
|
+ icon = { 'icon-thumb-menu icon-rec' }
|
|
1074
|
+ key = 'localrecording'
|
|
1075
|
+ onClick = {
|
|
1076
|
+ this._onToolbarOpenLocalRecordingInfoDialog
|
|
1077
|
+ }
|
|
1078
|
+ text = { t('localRecording.dialogTitle') } />
|
|
1079
|
+ );
|
|
1080
|
+ default:
|
|
1081
|
+ return null;
|
|
1082
|
+ }
|
|
1083
|
+ });
|
|
1084
|
+ }
|
|
1085
|
+
|
927
|
1086
|
/**
|
928
|
1087
|
* Renders the toolbox content.
|
929
|
1088
|
*
|
|
@@ -941,13 +1100,86 @@ class Toolbox extends Component<Props> {
|
941
|
1100
|
const overflowHasItems = Boolean(overflowMenuContent.filter(
|
942
|
1101
|
child => child).length);
|
943
|
1102
|
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
|
|
1103
|
+ const buttonsLeft = [];
|
|
1104
|
+ const buttonsRight = [];
|
|
1105
|
+
|
|
1106
|
+ const maxNumberOfButtonsPerGroup = Math.floor(
|
|
1107
|
+ (
|
|
1108
|
+ this.state.windowWidth
|
|
1109
|
+ - 168 // the width of the central group by design
|
|
1110
|
+ - 48 // the minimum space between the button groups
|
|
1111
|
+ )
|
|
1112
|
+ / 56 // the width + padding of a button
|
|
1113
|
+ / 2 // divide by the number of groups(left and right group)
|
|
1114
|
+ );
|
|
1115
|
+
|
|
1116
|
+ if (this._shouldShowButton('desktop')
|
|
1117
|
+ && this._isDesktopSharingButtonVisible()) {
|
|
1118
|
+ buttonsLeft.push('desktop');
|
|
1119
|
+ }
|
|
1120
|
+ if (this._shouldShowButton('raisehand')) {
|
|
1121
|
+ buttonsLeft.push('raisehand');
|
|
1122
|
+ }
|
|
1123
|
+ if (this._shouldShowButton('chat')) {
|
|
1124
|
+ buttonsLeft.push('chat');
|
|
1125
|
+ }
|
|
1126
|
+ if (this._shouldShowButton('closedcaptions')) {
|
|
1127
|
+ buttonsLeft.push('closedcaptions');
|
|
1128
|
+ }
|
|
1129
|
+ if (overflowHasItems) {
|
|
1130
|
+ buttonsRight.push('overflowmenu');
|
|
1131
|
+ }
|
|
1132
|
+ if (this._shouldShowButton('info')) {
|
|
1133
|
+ buttonsRight.push('info');
|
|
1134
|
+ }
|
|
1135
|
+ if (this._shouldShowButton('invite') && !_hideInviteButton) {
|
|
1136
|
+ buttonsRight.push('invite');
|
|
1137
|
+ }
|
|
1138
|
+ if (this._shouldShowButton('tileview')) {
|
|
1139
|
+ buttonsRight.push('tileview');
|
|
1140
|
+ }
|
|
1141
|
+ if (this._shouldShowButton('localrecording')) {
|
|
1142
|
+ buttonsRight.push('localrecording');
|
|
1143
|
+ }
|
|
1144
|
+
|
|
1145
|
+ const movedButtons = [];
|
|
1146
|
+
|
|
1147
|
+ if (buttonsLeft.length > maxNumberOfButtonsPerGroup) {
|
|
1148
|
+ movedButtons.push(...buttonsLeft.splice(
|
|
1149
|
+ maxNumberOfButtonsPerGroup,
|
|
1150
|
+ buttonsLeft.length - maxNumberOfButtonsPerGroup));
|
|
1151
|
+ if (buttonsRight.indexOf('overflowmenu') === -1) {
|
|
1152
|
+ buttonsRight.unshift('overflowmenu');
|
|
1153
|
+ }
|
|
1154
|
+ }
|
|
1155
|
+
|
|
1156
|
+ if (buttonsRight.length > maxNumberOfButtonsPerGroup) {
|
|
1157
|
+ if (buttonsRight.indexOf('overflowmenu') === -1) {
|
|
1158
|
+ buttonsRight.unshift('overflowmenu');
|
|
1159
|
+ }
|
|
1160
|
+
|
|
1161
|
+ let numberOfButtons = maxNumberOfButtonsPerGroup;
|
|
1162
|
+
|
|
1163
|
+ // make sure the more button will be displayed when we move buttons.
|
|
1164
|
+ if (numberOfButtons === 0) {
|
|
1165
|
+ numberOfButtons++;
|
|
1166
|
+ }
|
|
1167
|
+
|
|
1168
|
+ movedButtons.push(...buttonsRight.splice(
|
|
1169
|
+ numberOfButtons,
|
|
1170
|
+ buttonsRight.length - numberOfButtons));
|
|
1171
|
+
|
|
1172
|
+ }
|
|
1173
|
+
|
|
1174
|
+ overflowMenuContent.splice(
|
|
1175
|
+ 1, 0, ...this._renderMovedButtons(movedButtons));
|
944
|
1176
|
|
945
|
1177
|
return (
|
946
|
1178
|
<div className = 'toolbox-content'>
|
947
|
1179
|
<div className = 'button-group-left'>
|
948
|
|
- { this._shouldShowButton('desktop')
|
|
1180
|
+ { buttonsLeft.indexOf('desktop') !== -1
|
949
|
1181
|
&& this._renderDesktopSharingButton() }
|
950
|
|
- { this._shouldShowButton('raisehand')
|
|
1182
|
+ { buttonsLeft.indexOf('raisehand') !== -1
|
951
|
1183
|
&& <ToolbarButton
|
952
|
1184
|
accessibilityLabel =
|
953
|
1185
|
{
|
|
@@ -958,7 +1190,7 @@ class Toolbox extends Component<Props> {
|
958
|
1190
|
: 'icon-raised-hand' }
|
959
|
1191
|
onClick = { this._onToolbarToggleRaiseHand }
|
960
|
1192
|
tooltip = { t('toolbar.raiseHand') } /> }
|
961
|
|
- { this._shouldShowButton('chat')
|
|
1193
|
+ { buttonsLeft.indexOf('chat') !== -1
|
962
|
1194
|
&& <div className = 'toolbar-button-with-badge'>
|
963
|
1195
|
<ToolbarButton
|
964
|
1196
|
accessibilityLabel =
|
|
@@ -971,7 +1203,7 @@ class Toolbox extends Component<Props> {
|
971
|
1203
|
<ChatCounter />
|
972
|
1204
|
</div> }
|
973
|
1205
|
{
|
974
|
|
- this._shouldShowButton('closedcaptions')
|
|
1206
|
+ buttonsLeft.indexOf('closedcaptions') !== -1
|
975
|
1207
|
&& <ClosedCaptionButton />
|
976
|
1208
|
}
|
977
|
1209
|
</div>
|
|
@@ -984,24 +1216,26 @@ class Toolbox extends Component<Props> {
|
984
|
1216
|
visible = { this._shouldShowButton('camera') } />
|
985
|
1217
|
</div>
|
986
|
1218
|
<div className = 'button-group-right'>
|
987
|
|
- { this._shouldShowButton('localrecording')
|
|
1219
|
+ { buttonsRight.indexOf('localrecording') !== -1
|
988
|
1220
|
&& <LocalRecordingButton
|
989
|
1221
|
onClick = {
|
990
|
1222
|
this._onToolbarOpenLocalRecordingInfoDialog
|
991
|
1223
|
} />
|
992
|
1224
|
}
|
993
|
|
- { this._shouldShowButton('tileview')
|
|
1225
|
+ { buttonsRight.indexOf('tileview') !== -1
|
994
|
1226
|
&& <TileViewButton /> }
|
995
|
|
- { this._shouldShowButton('invite')
|
996
|
|
- && !_hideInviteButton
|
|
1227
|
+ { buttonsRight.indexOf('invite') !== -1
|
997
|
1228
|
&& <ToolbarButton
|
998
|
1229
|
accessibilityLabel =
|
999
|
1230
|
{ t('toolbar.accessibilityLabel.invite') }
|
1000
|
1231
|
iconName = 'icon-invite'
|
1001
|
1232
|
onClick = { this._onToolbarOpenInvite }
|
1002
|
1233
|
tooltip = { t('toolbar.invite') } /> }
|
1003
|
|
- { this._shouldShowButton('info') && <InfoDialogButton /> }
|
1004
|
|
- { overflowHasItems
|
|
1234
|
+ {
|
|
1235
|
+ buttonsRight.indexOf('info') !== -1
|
|
1236
|
+ && <InfoDialogButton />
|
|
1237
|
+ }
|
|
1238
|
+ { buttonsRight.indexOf('overflowmenu') !== -1
|
1005
|
1239
|
&& <OverflowMenuButton
|
1006
|
1240
|
isOpen = { _overflowMenuVisible }
|
1007
|
1241
|
onVisibilityChange = { this._onSetOverflowVisible }>
|