React Toolbar
This commit is contained in:
parent
0d7cb63978
commit
da4425b5c0
|
@ -20,6 +20,8 @@ import analytics from './modules/analytics/analytics';
|
|||
|
||||
import EventEmitter from "events";
|
||||
|
||||
import { showDesktopSharingButton } from './react/features/toolbar';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
|
@ -583,6 +585,9 @@ export default {
|
|||
APP.connection = connection = con;
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
|
||||
APP.store.dispatch(showDesktopSharingButton());
|
||||
|
||||
APP.remoteControl.init();
|
||||
this._createRoom(tracks);
|
||||
|
||||
|
|
|
@ -67,17 +67,3 @@
|
|||
from { width: 0; }
|
||||
to { width: $sidebarWidth; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade in / out animations
|
||||
**/
|
||||
|
||||
@include keyframes(fadeIn) {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@include keyframes(fadeOut) {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
.notice {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
z-index: $zindex3;
|
||||
margin-top: 6px;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
&__message {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
|
|
|
@ -1,59 +1,138 @@
|
|||
/**
|
||||
* Round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
background-color: $toolbarBadgeBackground;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
color: $toolbarBadgeColor;
|
||||
// Do not inherit the font-family from the toolbar button, because it's an
|
||||
// icon style.
|
||||
font-family: $baseFontFamily;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 13px;
|
||||
min-width: 13px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar button styles.
|
||||
*/
|
||||
.button {
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
z-index: $zindex1;
|
||||
display: inline-block;
|
||||
font-size: $toolbarFontSize !important;
|
||||
height: 50px;
|
||||
line-height: 50px !important;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top:0px;
|
||||
vertical-align: middle;
|
||||
width: 50px;
|
||||
|
||||
&_hangup {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize !important;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:not(.toggled) {
|
||||
&:hover, &:active {
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: $toolbarToggleBackground;
|
||||
|
||||
&.icon-camera {
|
||||
@extend .icon-camera-disabled;
|
||||
}
|
||||
|
||||
&.icon-full-screen {
|
||||
@extend .icon-exit-full-screen;
|
||||
}
|
||||
|
||||
&.icon-microphone {
|
||||
@extend .icon-mic-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: block;
|
||||
left:0;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right:0;
|
||||
text-align: center;
|
||||
top:0;
|
||||
z-index: $toolbarZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common toolbar styles.
|
||||
*/
|
||||
.toolbar {
|
||||
background-color: $toolbarBackground;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
/**
|
||||
* Splitter button in the toolbar.
|
||||
*/
|
||||
&__splitter {
|
||||
background: $splitterColor;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 1px;
|
||||
height: 50%;
|
||||
margin: 0 $splitterToolbarButtonMargin;
|
||||
background: $splitterColor;
|
||||
}
|
||||
vertical-align: middle;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
#mainToolbarContainer{
|
||||
display: block;
|
||||
/**
|
||||
* Primary toolbar styles.
|
||||
*/
|
||||
&_primary {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
z-index: $toolbarZ;
|
||||
pointer-events: none;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#subject {
|
||||
position: relative;
|
||||
z-index: $zindex3;
|
||||
width: auto;
|
||||
padding: 5px;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
text-align: center;
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
|
||||
#mainToolbar {
|
||||
height: $defaultToolbarSize;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: $defaultToolbarSize;
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
.button:first-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
|
@ -64,121 +143,92 @@
|
|||
}
|
||||
}
|
||||
|
||||
#extendedToolbar {
|
||||
&_primary a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary toolbar styles.
|
||||
*/
|
||||
&_secondary {
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
width: $defaultToolbarSize;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-top: 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
left: 0;
|
||||
padding-top: 10px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
width: $defaultToolbarSize;
|
||||
-webkit-transform: translateX(-100%);
|
||||
}
|
||||
|
||||
#toolbar_button_hangup {
|
||||
color: #BF2117;
|
||||
font-size: $hangupFontSize !important;
|
||||
}
|
||||
|
||||
#toolbar_button_etherpad {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mainToolbar a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #FFFFFF;
|
||||
top:0px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
z-index: $zindex1;
|
||||
font-size: $toolbarFontSize !important;
|
||||
line-height: 50px !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.button.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button.toggled {
|
||||
background: $toolbarToggleBackground !important;
|
||||
}
|
||||
|
||||
a.button.unclickable:hover,
|
||||
a.button.unclickable:active,
|
||||
a.button.unclickable.selected{
|
||||
cursor: default;
|
||||
background: none;
|
||||
}
|
||||
|
||||
a.button:hover,
|
||||
a.button:active,
|
||||
a.button.selected {
|
||||
.button.toggled:not(.icon-raised-hand) {
|
||||
background: $toolbarSelectBackground;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
width: 30px;
|
||||
border-radius: 50%;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
background-color: $toolbarBadgeBackground;
|
||||
color: $toolbarBadgeColor;
|
||||
font-size: 9px;
|
||||
line-height: 13px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
min-width: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
// Do not inherit the font-family from the toolbar button, because it's an
|
||||
// icon style.
|
||||
font-family: $baseFontFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar specific round badge.
|
||||
*/
|
||||
.toolbar .badge-round {
|
||||
.badge-round {
|
||||
bottom: 9px;
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
bottom: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.subject {
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
z-index: $zindex3;
|
||||
|
||||
&.subject_slide-in {
|
||||
top: 80px;
|
||||
@include transition(top .3s ease-in);
|
||||
}
|
||||
|
||||
&.subject_slide-out {
|
||||
top: 0;
|
||||
@include transition(top .3s ease-out);
|
||||
}
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
border-radius: 50%;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,9 +322,13 @@ a.button>#avatar {
|
|||
* START of fade in animation for main toolbar
|
||||
*/
|
||||
.fadeIn {
|
||||
@include animation('fadeIn .3s linear .2s forwards');
|
||||
opacity: 1;
|
||||
|
||||
@include transition(all .3s ease-in);
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
@include animation('fadeOut .5s linear forwards');
|
||||
opacity: 0;
|
||||
|
||||
@include transition(all .3s ease-out);
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
* Style variables
|
||||
*/
|
||||
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$hangupColor: #bf2117;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
/**
|
||||
* Size variables.
|
||||
*/
|
||||
$defaultToolbarSize: 50px;
|
||||
|
||||
// Video layout.
|
||||
$thumbnailToolbarHeight: 22px;
|
||||
|
@ -34,14 +33,16 @@ $tooltipBg: rgba(0,0,0, 0.7);
|
|||
/**
|
||||
* Toolbar
|
||||
*/
|
||||
$toolbarTitleColor: #FFFFFF;
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$defaultToolbarSize: 50px;
|
||||
$splitterToolbarButtonMargin: 18px;
|
||||
$toolbarBackground: rgba(0, 0, 0, 0.5);
|
||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||
$toolbarBadgeBackground: #165ECC;
|
||||
$toolbarBadgeColor: #FFFFFF;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||
$toolbarTitleColor: #FFFFFF;
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$toolbarToggleBackground: #12499C;
|
||||
$splitterToolbarButtonMargin: 18px;
|
||||
|
||||
/**
|
||||
* Main controls
|
||||
|
|
|
@ -6,8 +6,6 @@ var UI = {};
|
|||
|
||||
import Chat from "./side_pannels/chat/Chat";
|
||||
import SidePanels from "./side_pannels/SidePanels";
|
||||
import Toolbar from "./toolbars/Toolbar";
|
||||
import ToolbarToggler from "./toolbars/ToolbarToggler";
|
||||
import Avatar from "./avatar/Avatar";
|
||||
import SideContainerToggler from "./side_pannels/SideContainerToggler";
|
||||
import UIUtil from "./util/UIUtil";
|
||||
|
@ -25,6 +23,23 @@ import RingOverlay from "./ring_overlay/RingOverlay";
|
|||
import UIErrors from './UIErrors';
|
||||
import { debounce } from "../util/helpers";
|
||||
|
||||
import {
|
||||
setAudioMuted,
|
||||
setVideoMuted
|
||||
} from '../../react/features/base/media';
|
||||
import {
|
||||
checkAutoEnableDesktopSharing,
|
||||
dockToolbar,
|
||||
setAudioIconEnabled,
|
||||
setToolbarButton,
|
||||
setVideoIconEnabled,
|
||||
showDialPadButton,
|
||||
showEtherpadButton,
|
||||
showSharedVideoButton,
|
||||
showSIPCallButton,
|
||||
showToolbar
|
||||
} from '../../react/features/toolbar';
|
||||
|
||||
var EventEmitter = require("events");
|
||||
UI.messageHandler = require("./util/MessageHandler");
|
||||
var messageHandler = UI.messageHandler;
|
||||
|
@ -83,16 +98,6 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
|
|||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= "dialog.micNotSendingData";
|
||||
|
||||
/**
|
||||
* Initialize toolbars with side panels.
|
||||
*/
|
||||
function setupToolbars() {
|
||||
// Initialize toolbar buttons
|
||||
Toolbar.init(eventEmitter);
|
||||
// Initialize side panels
|
||||
SidePanels.init(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the application in and out of full screen mode
|
||||
* (a.k.a. presentation mode in Chrome).
|
||||
|
@ -231,7 +236,7 @@ UI.initConference = function () {
|
|||
UI.setUserAvatarID(id, Settings.getAvatarId());
|
||||
}
|
||||
|
||||
Toolbar.checkAutoEnableDesktopSharing();
|
||||
APP.store.dispatch(checkAutoEnableDesktopSharing());
|
||||
|
||||
if(!interfaceConfig.filmStripOnly) {
|
||||
Feedback.init(eventEmitter);
|
||||
|
@ -294,7 +299,6 @@ UI.start = function () {
|
|||
// Set the defaults for tooltips.
|
||||
_setTooltipDefaults();
|
||||
|
||||
ToolbarToggler.init();
|
||||
SideContainerToggler.init(eventEmitter);
|
||||
FilmStrip.init(eventEmitter);
|
||||
|
||||
|
@ -313,11 +317,9 @@ UI.start = function () {
|
|||
{ leading: true, trailing: false });
|
||||
|
||||
$("#videoconference_page").mousemove(debouncedShowToolbar);
|
||||
setupToolbars();
|
||||
|
||||
// Initialise the recording module.
|
||||
if (config.enableRecording)
|
||||
Recording.init(eventEmitter, config.recordingType);
|
||||
// Initialize side panels
|
||||
SidePanels.init(eventEmitter);
|
||||
} else {
|
||||
$("body").addClass("filmstrip-only");
|
||||
UIUtil.setVisible('mainToolbarContainer', false);
|
||||
|
@ -446,7 +448,8 @@ UI.initEtherpad = name => {
|
|||
logger.log('Etherpad is enabled');
|
||||
etherpadManager
|
||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||
Toolbar.showEtherpadButton();
|
||||
|
||||
APP.store.dispatch(showEtherpadButton());
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -521,8 +524,9 @@ UI.onPeerVideoTypeChanged
|
|||
UI.updateLocalRole = isModerator => {
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
Toolbar.showSharedVideoButton(isModerator);
|
||||
APP.store.dispatch(showSIPCallButton(isModerator));
|
||||
APP.store.dispatch(showSharedVideoButton());
|
||||
|
||||
Recording.showRecordingButton(isModerator);
|
||||
SettingsMenu.showStartMutedOptions(isModerator);
|
||||
SettingsMenu.showFollowMeOptions(isModerator);
|
||||
|
@ -676,7 +680,10 @@ UI.askForNickname = function () {
|
|||
UI.setAudioMuted = function (id, muted) {
|
||||
VideoLayout.onAudioMute(id, muted);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.toggleAudioIcon(muted);
|
||||
APP.store.dispatch(setAudioMuted(muted));
|
||||
APP.store.dispatch(setToolbarButton('microphone', {
|
||||
toggled: muted
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -686,7 +693,10 @@ UI.setAudioMuted = function (id, muted) {
|
|||
UI.setVideoMuted = function (id, muted) {
|
||||
VideoLayout.onVideoMute(id, muted);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.toggleVideoIcon(muted);
|
||||
APP.store.dispatch(setVideoMuted(muted));
|
||||
APP.store.dispatch(setToolbarButton('camera', {
|
||||
toggled: muted
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -716,7 +726,7 @@ UI.removeListener = function (type, listener) {
|
|||
* @param type the type of the event we're emitting
|
||||
* @param options the parameters for the event
|
||||
*/
|
||||
UI.emitEvent = (type, options) => eventEmitter.emit(type, options);
|
||||
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
|
||||
|
||||
UI.clickOnVideo = function (videoNumber) {
|
||||
let videos = $("#remoteVideos .videocontainer:not(#mixedstream)");
|
||||
|
@ -731,12 +741,12 @@ UI.clickOnVideo = function (videoNumber) {
|
|||
|
||||
//Used by torture
|
||||
UI.showToolbar = function (timeout) {
|
||||
return ToolbarToggler.showToolbar(timeout);
|
||||
APP.store.dispatch(showToolbar(timeout));
|
||||
};
|
||||
|
||||
//Used by torture
|
||||
UI.dockToolbar = function (isDock) {
|
||||
ToolbarToggler.dockToolbar(isDock);
|
||||
APP.store.dispatch(dockToolbar(isDock));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -916,10 +926,14 @@ UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
|
|||
|
||||
/**
|
||||
* Update state of desktop sharing buttons.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.updateDesktopSharingButtons = function () {
|
||||
Toolbar.updateDesktopSharingButtonState();
|
||||
};
|
||||
UI.updateDesktopSharingButtons
|
||||
= () =>
|
||||
APP.store.dispatch(setToolbarButton('desktop', {
|
||||
toggled: APP.conference.isSharingScreen
|
||||
}));
|
||||
|
||||
/**
|
||||
* Hide connection quality statistics from UI.
|
||||
|
@ -970,11 +984,8 @@ UI.addMessage = function (from, displayName, message, stamp) {
|
|||
Chat.updateChatConversation(from, displayName, message, stamp);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
UI.updateDTMFSupport = function (isDTMFSupported) {
|
||||
//TODO: enable when the UI is ready
|
||||
//Toolbar.showDialPadButton(isDTMFSupported);
|
||||
};
|
||||
UI.updateDTMFSupport
|
||||
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
|
||||
|
||||
/**
|
||||
* Show user feedback dialog if its required and enabled after pressing the
|
||||
|
@ -1315,7 +1326,8 @@ UI.onSharedVideoStop = function (id, attributes) {
|
|||
* @param {boolean} enabled indicates if the camera button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
|
||||
UI.setCameraButtonEnabled
|
||||
= enabled => APP.store.dispatch(setVideoIconEnabled(enabled));
|
||||
|
||||
/**
|
||||
* Enables / disables microphone toolbar button.
|
||||
|
@ -1323,7 +1335,8 @@ UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
|
|||
* @param {boolean} enabled indicates if the microphone button should be
|
||||
* enabled or disabled
|
||||
*/
|
||||
UI.setMicrophoneButtonEnabled = enabled => Toolbar.setAudioIconEnabled(enabled);
|
||||
UI.setMicrophoneButtonEnabled
|
||||
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||
|
|
|
@ -20,7 +20,8 @@ import UIEvents from "../../../service/UI/UIEvents";
|
|||
import UIUtil from '../util/UIUtil';
|
||||
import VideoLayout from '../videolayout/VideoLayout';
|
||||
import Feedback from '../feedback/Feedback.js';
|
||||
import Toolbar from '../toolbars/Toolbar';
|
||||
|
||||
import { hideToolbar } from '../../../react/features/toolbar';
|
||||
|
||||
/**
|
||||
* The dialog for user input.
|
||||
|
@ -263,7 +264,7 @@ var Recording = {
|
|||
APP.conference.getMyUserId(), false);
|
||||
VideoLayout.setLocalVideoVisible(false);
|
||||
Feedback.enableFeedback(false);
|
||||
Toolbar.enable(false);
|
||||
APP.store.dispatch(hideToolbar());
|
||||
APP.UI.messageHandler.enableNotifications(false);
|
||||
APP.UI.messageHandler.enablePopups(false);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import VideoLayout from "../videolayout/VideoLayout";
|
|||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import SmallVideo from '../videolayout/SmallVideo';
|
||||
import FilmStrip from '../videolayout/FilmStrip';
|
||||
import ToolbarToggler from "../toolbars/ToolbarToggler";
|
||||
|
||||
import { dockToolbar, showToolbar } from '../../../react/features/toolbar';
|
||||
|
||||
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
||||
|
||||
|
@ -578,7 +579,7 @@ class SharedVideoContainer extends LargeContainer {
|
|||
self.bodyBackground = document.body.style.background;
|
||||
document.body.style.background = 'black';
|
||||
this.$iframe.css({opacity: 1});
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
APP.store.dispatch(dockToolbar(true));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
@ -586,7 +587,7 @@ class SharedVideoContainer extends LargeContainer {
|
|||
|
||||
hide () {
|
||||
let self = this;
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
APP.store.dispatch(dockToolbar(false));
|
||||
return new Promise(resolve => {
|
||||
this.$iframe.fadeOut(300, () => {
|
||||
document.body.style.background = self.bodyBackground;
|
||||
|
@ -597,7 +598,7 @@ class SharedVideoContainer extends LargeContainer {
|
|||
}
|
||||
|
||||
onHoverIn () {
|
||||
ToolbarToggler.showToolbar();
|
||||
APP.store.dispatch(showToolbar());
|
||||
}
|
||||
|
||||
get id () {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import {processReplacements, linkify} from './Replacement';
|
||||
import CommandsProcessor from './Commands';
|
||||
import ToolbarToggler from '../../toolbars/ToolbarToggler';
|
||||
import VideoLayout from "../../videolayout/VideoLayout";
|
||||
|
||||
import UIUtil from '../../util/UIUtil';
|
||||
|
@ -10,6 +9,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
|||
|
||||
import { smileys } from './smileys';
|
||||
|
||||
import { dockToolbar, setSubject } from '../../../../react/features/toolbar';
|
||||
|
||||
let unreadMessages = 0;
|
||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||
const htmlStr = `
|
||||
|
@ -59,7 +60,7 @@ function updateVisualNotification() {
|
|||
if (unreadMessages) {
|
||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
APP.store.dispatch(dockToolbar(true));
|
||||
|
||||
const chatButtonElement
|
||||
= document.getElementById('toolbar_button_chat');
|
||||
|
@ -238,7 +239,7 @@ var Chat = {
|
|||
// Undock the toolbar when the chat is shown and if we're in a
|
||||
// video mode.
|
||||
if (VideoLayout.isLargeVideoVisible()) {
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
APP.store.dispatch(dockToolbar(false));
|
||||
}
|
||||
|
||||
// if we are in conversation mode focus on the text input
|
||||
|
@ -319,10 +320,9 @@ var Chat = {
|
|||
subject = subject.trim();
|
||||
}
|
||||
|
||||
let subjectId = 'subject';
|
||||
const html = linkify(UIUtil.escapeHtml(subject));
|
||||
$(`#${subjectId}`).html(html);
|
||||
UIUtil.setVisible(subjectId, subject && subject.length > 0);
|
||||
|
||||
APP.store.dispatch(setSubject(html));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,889 +0,0 @@
|
|||
/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import SideContainerToggler from "../side_pannels/SideContainerToggler";
|
||||
|
||||
let emitter = null;
|
||||
let Toolbar;
|
||||
|
||||
/**
|
||||
* Handlers for toolbar buttons.
|
||||
*
|
||||
* buttonId {string}: handler {function}
|
||||
*/
|
||||
const buttonHandlers = {
|
||||
"toolbar_button_profile": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.profile.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_PROFILE);
|
||||
},
|
||||
"toolbar_button_mute": function () {
|
||||
let sharedVideoManager = APP.UI.getSharedVideoManager();
|
||||
|
||||
if (APP.conference.audioMuted) {
|
||||
// If there's a shared video with the volume "on" and we aren't
|
||||
// the video owner, we warn the user
|
||||
// that currently it's not possible to unmute.
|
||||
if (sharedVideoManager
|
||||
&& sharedVideoManager.isSharedVideoVolumeOn()
|
||||
&& !sharedVideoManager.isSharedVideoOwner()) {
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'#unableToUnmutePopup', true, 5000);
|
||||
}
|
||||
else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
|
||||
emitter.emit(UIEvents.AUDIO_MUTED, false, true);
|
||||
}
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
|
||||
emitter.emit(UIEvents.AUDIO_MUTED, true, true);
|
||||
}
|
||||
},
|
||||
"toolbar_button_camera": function () {
|
||||
if (APP.conference.videoMuted) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
|
||||
emitter.emit(UIEvents.VIDEO_MUTED, false);
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
|
||||
emitter.emit(UIEvents.VIDEO_MUTED, true);
|
||||
}
|
||||
},
|
||||
"toolbar_button_link": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
|
||||
emitter.emit(UIEvents.INVITE_CLICKED);
|
||||
},
|
||||
"toolbar_button_chat": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_CHAT);
|
||||
},
|
||||
"toolbar_contact_list": function () {
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'toolbar.contacts.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_CONTACT_LIST);
|
||||
},
|
||||
"toolbar_button_etherpad": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
|
||||
emitter.emit(UIEvents.ETHERPAD_CLICKED);
|
||||
},
|
||||
"toolbar_button_sharedvideo": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
|
||||
emitter.emit(UIEvents.SHARED_VIDEO_CLICKED);
|
||||
},
|
||||
"toolbar_button_desktopsharing": function () {
|
||||
if (APP.conference.isSharingScreen) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
|
||||
}
|
||||
emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
|
||||
},
|
||||
"toolbar_button_fullScreen": function() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
|
||||
|
||||
emitter.emit(UIEvents.TOGGLE_FULLSCREEN);
|
||||
},
|
||||
"toolbar_button_sip": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
|
||||
showSipNumberInput();
|
||||
},
|
||||
"toolbar_button_dialpad": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
|
||||
dialpadButtonClicked();
|
||||
},
|
||||
"toolbar_button_settings": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_SETTINGS);
|
||||
},
|
||||
"toolbar_button_hangup": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
|
||||
emitter.emit(UIEvents.HANGUP);
|
||||
},
|
||||
"toolbar_button_raisehand": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All toolbars buttons description
|
||||
*/
|
||||
const defaultToolbarButtons = {
|
||||
'microphone': {
|
||||
id: 'toolbar_button_mute',
|
||||
tooltipKey: 'toolbar.mute',
|
||||
className: "button icon-microphone",
|
||||
shortcut: 'M',
|
||||
shortcutAttr: 'mutePopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
|
||||
APP.conference.toggleAudioMuted();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.mute",
|
||||
popups: [
|
||||
{
|
||||
id: 'micMutedPopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.micMutedPopup'
|
||||
},
|
||||
{
|
||||
id: 'unableToUnmutePopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.unableToUnmutePopup'
|
||||
},
|
||||
{
|
||||
id: 'talkWhileMutedPopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.talkWhileMutedPopup'
|
||||
}
|
||||
],
|
||||
content: "Mute / Unmute",
|
||||
i18n: "[content]toolbar.mute"
|
||||
},
|
||||
'camera': {
|
||||
id: 'toolbar_button_camera',
|
||||
tooltipKey: 'toolbar.videomute',
|
||||
className: "button icon-camera",
|
||||
shortcut: 'V',
|
||||
shortcutAttr: 'toggleVideoPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
|
||||
APP.conference.toggleVideoMuted();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.videoMute",
|
||||
content: "Start / stop camera",
|
||||
i18n: "[content]toolbar.videomute"
|
||||
},
|
||||
'desktop': {
|
||||
id: 'toolbar_button_desktopsharing',
|
||||
tooltipKey: 'toolbar.sharescreen',
|
||||
className: 'button icon-share-desktop',
|
||||
shortcut: 'D',
|
||||
shortcutAttr: 'toggleDesktopSharingPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
|
||||
APP.conference.toggleScreenSharing();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
|
||||
content: 'Share screen',
|
||||
i18n: '[content]toolbar.sharescreen'
|
||||
},
|
||||
'invite': {
|
||||
id: 'toolbar_button_link',
|
||||
tooltipKey: 'toolbar.invite',
|
||||
className: 'button icon-link',
|
||||
content: 'Invite others',
|
||||
i18n: '[content]toolbar.invite'
|
||||
},
|
||||
'chat': {
|
||||
id: 'toolbar_button_chat',
|
||||
tooltipKey: 'toolbar.chat',
|
||||
className: 'button icon-chat',
|
||||
shortcut: 'C',
|
||||
shortcutAttr: 'toggleChatPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleChat',
|
||||
sideContainerId: 'chat_container',
|
||||
html: `<span class="badge-round">
|
||||
<span id="unreadMessages"></span>
|
||||
</span>`
|
||||
},
|
||||
'contacts': {
|
||||
id: 'toolbar_contact_list',
|
||||
tooltipKey: 'bottomtoolbar.contactlist',
|
||||
className: 'button icon-contactList',
|
||||
sideContainerId: 'contacts_container',
|
||||
html: `<span class="badge-round">
|
||||
<span id="numberOfParticipants"></span>
|
||||
</span>`
|
||||
},
|
||||
'profile': {
|
||||
id: 'toolbar_button_profile',
|
||||
tooltipKey: 'profile.setDisplayNameLabel',
|
||||
className: 'button',
|
||||
sideContainerId: 'profile_container',
|
||||
html: `<img id="avatar" src="images/avatar2.png"/>`
|
||||
},
|
||||
'etherpad': {
|
||||
id: 'toolbar_button_etherpad',
|
||||
tooltipKey: 'toolbar.etherpad',
|
||||
className: 'button icon-share-doc'
|
||||
},
|
||||
'fullscreen': {
|
||||
id: 'toolbar_button_fullScreen',
|
||||
tooltipKey: 'toolbar.fullscreen',
|
||||
className: "button icon-full-screen",
|
||||
shortcut: 'S',
|
||||
shortcutAttr: 'toggleFullscreenPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
|
||||
APP.UI.toggleFullScreen();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.fullScreen",
|
||||
content: "Enter / Exit Full Screen",
|
||||
i18n: "[content]toolbar.fullscreen"
|
||||
},
|
||||
'settings': {
|
||||
id: 'toolbar_button_settings',
|
||||
tooltipKey: 'toolbar.Settings',
|
||||
className: 'button icon-settings',
|
||||
sideContainerId: "settings_container"
|
||||
},
|
||||
'hangup': {
|
||||
id: 'toolbar_button_hangup',
|
||||
tooltipKey: 'toolbar.hangup',
|
||||
className: "button icon-hangup",
|
||||
content: "Hang Up",
|
||||
i18n: "[content]toolbar.hangup"
|
||||
},
|
||||
'raisehand': {
|
||||
id: "toolbar_button_raisehand",
|
||||
tooltipKey: 'toolbar.raiseHand',
|
||||
className: "button icon-raised-hand",
|
||||
shortcut: "R",
|
||||
shortcutAttr: "raiseHandPopover",
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent("shortcut.raisehand.clicked");
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.raiseHand",
|
||||
content: "Raise Hand",
|
||||
i18n: "[content]toolbar.raiseHand"
|
||||
},
|
||||
//init and btn handler: Recording.initRecordingButton (Recording.js)
|
||||
'recording': {
|
||||
id: 'toolbar_button_record',
|
||||
tooltipKey: 'liveStreaming.buttonTooltip',
|
||||
className: 'button',
|
||||
hidden: true // will be displayed once
|
||||
// the recording functionality is detected
|
||||
},
|
||||
'sharedvideo': {
|
||||
id: 'toolbar_button_sharedvideo',
|
||||
tooltipKey: 'toolbar.sharedvideo',
|
||||
className: 'button icon-shared-video',
|
||||
popups: [
|
||||
{
|
||||
id: 'sharedVideoMutedPopup',
|
||||
className: 'loginmenu extendedToolbarPopup',
|
||||
dataAttr: '[title]toolbar.sharedVideoMutedPopup',
|
||||
dataAttrPosition: 'w'
|
||||
}
|
||||
]
|
||||
},
|
||||
'sip': {
|
||||
id: 'toolbar_button_sip',
|
||||
tooltipKey: 'toolbar.sip',
|
||||
className: 'button icon-telephone',
|
||||
hidden: true // will be displayed once
|
||||
// the SIP calls functionality is detected
|
||||
},
|
||||
'dialpad': {
|
||||
id: 'toolbar_button_dialpad',
|
||||
tooltipKey: 'toolbar.dialpad',
|
||||
className: 'button icon-dialpad',
|
||||
//TODO: remove it after UI.updateDTMFSupport fix
|
||||
hidden: true
|
||||
}
|
||||
};
|
||||
|
||||
function dialpadButtonClicked() {
|
||||
//TODO show the dialpad box
|
||||
}
|
||||
|
||||
function showSipNumberInput () {
|
||||
let defaultNumber = config.defaultSipNumber
|
||||
? config.defaultSipNumber
|
||||
: '';
|
||||
let titleKey = "dialog.sipMsg";
|
||||
let msgString = (`
|
||||
<input class="input-control"
|
||||
name="sipNumber" type="text"
|
||||
value="${defaultNumber}" autofocus>`);
|
||||
|
||||
APP.UI.messageHandler.openTwoButtonDialog({
|
||||
titleKey,
|
||||
msgString,
|
||||
leftButtonKey: "dialog.Dial",
|
||||
submitFunction: function (e, v, m, f) {
|
||||
if (v && f.sipNumber) {
|
||||
emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
|
||||
}
|
||||
},
|
||||
focus: ':input:first'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get place for toolbar button.
|
||||
* Now it can be in main toolbar or in extended (left) toolbar
|
||||
*
|
||||
* @param btn {string}
|
||||
* @returns {string}
|
||||
*/
|
||||
function getToolbarButtonPlace (btn) {
|
||||
return interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn) ?
|
||||
'main' :
|
||||
'extended';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for side toolbar container toggled event.
|
||||
*
|
||||
* @param {string} containerId - ID of the container.
|
||||
* @param {boolean} isVisible - Flag showing whether container
|
||||
* is visible.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSideToolbarContainerToggled(containerId, isVisible) {
|
||||
Toolbar._handleSideToolbarContainerToggled(containerId, isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for local raise hand changed event.
|
||||
*
|
||||
* @param {boolean} isRaisedHand - Flag showing whether hand is raised.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onLocalRaiseHandChanged(isRaisedHand) {
|
||||
Toolbar._setToggledState("toolbar_button_raisehand", isRaisedHand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for full screen toggled event.
|
||||
*
|
||||
* @param {boolean} isFullScreen - Flag showing whether app in full
|
||||
* screen mode.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onFullScreenToggled(isFullScreen) {
|
||||
Toolbar._handleFullScreenToggled(isFullScreen);
|
||||
}
|
||||
|
||||
Toolbar = {
|
||||
init (eventEmitter) {
|
||||
emitter = eventEmitter;
|
||||
// The toolbar is enabled by default.
|
||||
this.enabled = true;
|
||||
this.toolbarSelector = $("#mainToolbarContainer");
|
||||
this.extendedToolbarSelector = $("#extendedToolbar");
|
||||
|
||||
// Unregister listeners in case of reinitialization.
|
||||
this.unregisterListeners();
|
||||
|
||||
// Initialise the toolbar buttons.
|
||||
// The main toolbar will only take into account
|
||||
// it's own configuration from interface_config.
|
||||
this._initToolbarButtons();
|
||||
|
||||
this._setShortcutsAndTooltips();
|
||||
|
||||
this._setButtonHandlers();
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
|
||||
(popupID, show, timeout) => {
|
||||
Toolbar._showCustomToolbarPopup(popupID, show, timeout);
|
||||
});
|
||||
|
||||
if(!APP.tokenData.isGuest) {
|
||||
$("#toolbar_button_profile").addClass("unclickable");
|
||||
UIUtil.removeTooltip(
|
||||
document.getElementById('toolbar_button_profile'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Register listeners for UI events of toolbar component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
registerListeners() {
|
||||
APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
onSideToolbarContainerToggled);
|
||||
|
||||
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
onLocalRaiseHandChanged);
|
||||
|
||||
APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED, onFullScreenToggled);
|
||||
},
|
||||
/**
|
||||
* Unregisters handlers for UI events of Toolbar component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
unregisterListeners() {
|
||||
APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
onSideToolbarContainerToggled);
|
||||
|
||||
APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
onLocalRaiseHandChanged);
|
||||
|
||||
APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
|
||||
onFullScreenToggled);
|
||||
},
|
||||
/**
|
||||
* Enables / disables the toolbar.
|
||||
* @param {e} set to {true} to enable the toolbar or {false}
|
||||
* to disable it
|
||||
*/
|
||||
enable (e) {
|
||||
this.enabled = e;
|
||||
if (!e && this.isVisible())
|
||||
this.hide(false);
|
||||
},
|
||||
/**
|
||||
* Indicates if the bottom toolbar is currently enabled.
|
||||
* @return {this.enabled}
|
||||
*/
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
},
|
||||
|
||||
showEtherpadButton () {
|
||||
if (!$('#toolbar_button_etherpad').is(":visible")) {
|
||||
$('#toolbar_button_etherpad').css({display: 'inline-block'});
|
||||
}
|
||||
},
|
||||
|
||||
// Shows or hides the 'shared video' button.
|
||||
showSharedVideoButton () {
|
||||
let id = 'toolbar_button_sharedvideo';
|
||||
let shouldShow = UIUtil.isButtonEnabled('sharedvideo')
|
||||
&& !config.disableThirdPartyRequests;
|
||||
|
||||
if (shouldShow) {
|
||||
let el = document.getElementById(id);
|
||||
UIUtil.setTooltip(el, 'toolbar.sharedvideo', 'right');
|
||||
}
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
// checks whether desktop sharing is enabled and whether
|
||||
// we have params to start automatically sharing
|
||||
checkAutoEnableDesktopSharing () {
|
||||
if (UIUtil.isButtonEnabled('desktop')
|
||||
&& config.autoEnableDesktopSharing) {
|
||||
emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
|
||||
}
|
||||
},
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
showSipCallButton (show) {
|
||||
let shouldShow = APP.conference.sipGatewayEnabled()
|
||||
&& UIUtil.isButtonEnabled('sip') && show;
|
||||
let id = 'toolbar_button_sip';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
// Shows or hides the dialpad button
|
||||
showDialPadButton (show) {
|
||||
let shouldShow = UIUtil.isButtonEnabled('dialpad') && show;
|
||||
let id = 'toolbar_button_dialpad';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the state of the button. The button has blue glow if desktop
|
||||
* streaming is active.
|
||||
*/
|
||||
updateDesktopSharingButtonState () {
|
||||
this._setToggledState( "toolbar_button_desktopsharing",
|
||||
APP.conference.isSharingScreen);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks video icon as muted or not.
|
||||
*
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
toggleVideoIcon (muted) {
|
||||
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
||||
|
||||
this._setToggledState("toolbar_button_camera", muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled indicates if the button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
setVideoIconEnabled (enabled) {
|
||||
this._setMediaIconEnabled(
|
||||
'#toolbar_button_camera',
|
||||
enabled,
|
||||
/* data-i18n attribute value */
|
||||
`[content]toolbar.${enabled ? 'videomute' : 'cameraDisabled'}`,
|
||||
/* shortcut attribute value */
|
||||
'toggleVideoPopover');
|
||||
|
||||
enabled || this.toggleVideoIcon(!enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbar button associated with a specific media such
|
||||
* as audio or video.
|
||||
*
|
||||
* @param {string} btn - The jQuery selector <tt>string</tt> which
|
||||
* identifies the toolbar button to be enabled/disabled.
|
||||
* @param {boolean} enabled - <tt>true</tt> to enable the specified
|
||||
* <tt>btn</tt> or <tt>false</tt> to disable it.
|
||||
* @param {string} dataI18n - The value to assign to the <tt>data-i18n</tt>
|
||||
* attribute of the specified <tt>btn</tt>.
|
||||
* @param {string} shortcut - The value, if any, to assign to the
|
||||
* <tt>shortcut</tt> attribute of the specified <tt>btn</tt> if the toolbar
|
||||
* button is enabled.
|
||||
*/
|
||||
_setMediaIconEnabled(btn, enabled, dataI18n, shortcut) {
|
||||
const $btn = $(btn);
|
||||
|
||||
$btn
|
||||
.prop('disabled', !enabled)
|
||||
.attr('data-i18n', dataI18n)
|
||||
.attr('shortcut', enabled && shortcut ? shortcut : '');
|
||||
|
||||
enabled
|
||||
? $btn.removeAttr('disabled')
|
||||
: $btn.attr('disabled', 'disabled');
|
||||
|
||||
APP.translation.translateElement($btn);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks audio icon as muted or not.
|
||||
*
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
toggleAudioIcon(muted) {
|
||||
$('#toolbar_button_mute')
|
||||
.toggleClass("icon-microphone", !muted)
|
||||
.toggleClass("icon-mic-disabled", muted);
|
||||
|
||||
this._setToggledState("toolbar_button_mute", muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled indicates if the button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
setAudioIconEnabled (enabled) {
|
||||
this._setMediaIconEnabled(
|
||||
'#toolbar_button_mute',
|
||||
enabled,
|
||||
/* data-i18n attribute value */
|
||||
`[content]toolbar.${enabled ? 'mute' : 'micDisabled'}`,
|
||||
/* shortcut attribute value */
|
||||
'mutePopover');
|
||||
|
||||
enabled || this.toggleAudioIcon(!enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if the toolbar is currently hovered.
|
||||
* @return {boolean} true if the toolbar is currently hovered,
|
||||
* false otherwise
|
||||
*/
|
||||
isHovered() {
|
||||
var hovered = false;
|
||||
this.toolbarSelector.find('*').each(function () {
|
||||
let id = $(this).attr('id');
|
||||
if ($(`#${id}:hover`).length > 0) {
|
||||
hovered = true;
|
||||
// break each
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (hovered)
|
||||
return true;
|
||||
if ($("#bottomToolbar:hover").length > 0
|
||||
|| $("#extendedToolbar:hover").length > 0
|
||||
|| SideContainerToggler.isHovered()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this toolbar is currently visible, or false otherwise.
|
||||
* @return <tt>true</tt> if currently visible, <tt>false</tt> - otherwise
|
||||
*/
|
||||
isVisible() {
|
||||
return this.toolbarSelector.hasClass("fadeIn");
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the toolbar with animation or not depending on the animate
|
||||
* parameter.
|
||||
*/
|
||||
hide() {
|
||||
this.toolbarSelector
|
||||
.removeClass("fadeIn")
|
||||
.addClass("fadeOut");
|
||||
|
||||
let slideInAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideInExtX"
|
||||
: "slideInX";
|
||||
let slideOutAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideOutExtX"
|
||||
: "slideOutX";
|
||||
|
||||
this.extendedToolbarSelector.toggleClass(slideInAnimation)
|
||||
.toggleClass(slideOutAnimation);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the toolbar with animation or not depending on the animate
|
||||
* parameter.
|
||||
*/
|
||||
show() {
|
||||
if (this.toolbarSelector.hasClass("fadeOut")) {
|
||||
this.toolbarSelector.removeClass("fadeOut");
|
||||
}
|
||||
|
||||
let slideInAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideInExtX"
|
||||
: "slideInX";
|
||||
let slideOutAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideOutExtX"
|
||||
: "slideOutX";
|
||||
|
||||
if (this.extendedToolbarSelector.hasClass(slideOutAnimation)) {
|
||||
this.extendedToolbarSelector.toggleClass(slideOutAnimation);
|
||||
}
|
||||
|
||||
this.toolbarSelector.addClass("fadeIn");
|
||||
this.extendedToolbarSelector.toggleClass(slideInAnimation);
|
||||
},
|
||||
|
||||
registerClickListeners(listener) {
|
||||
$('#mainToolbarContainer').click(listener);
|
||||
|
||||
$("#extendedToolbar").click(listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the side toolbar toggle.
|
||||
*
|
||||
* @param {string} containerId the identifier of the container element
|
||||
*/
|
||||
_handleSideToolbarContainerToggled(containerId) {
|
||||
Object.keys(defaultToolbarButtons).forEach(
|
||||
id => {
|
||||
if (!UIUtil.isButtonEnabled(id))
|
||||
return;
|
||||
|
||||
var button = defaultToolbarButtons[id];
|
||||
|
||||
if (button.sideContainerId
|
||||
&& button.sideContainerId === containerId) {
|
||||
UIUtil.buttonClick(button.id, "selected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles full screen toggled.
|
||||
*
|
||||
* @param {boolean} isFullScreen indicates if we're currently in full
|
||||
* screen mode
|
||||
*/
|
||||
_handleFullScreenToggled(isFullScreen) {
|
||||
let element
|
||||
= document.getElementById("toolbar_button_fullScreen");
|
||||
|
||||
element.className = isFullScreen
|
||||
? element.className
|
||||
.replace("icon-full-screen", "icon-exit-full-screen")
|
||||
: element.className
|
||||
.replace("icon-exit-full-screen", "icon-full-screen");
|
||||
|
||||
Toolbar._setToggledState("toolbar_button_fullScreen", isFullScreen);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise toolbar buttons.
|
||||
*/
|
||||
_initToolbarButtons() {
|
||||
interfaceConfig.TOOLBAR_BUTTONS.forEach((value, index) => {
|
||||
let place = getToolbarButtonPlace(value);
|
||||
|
||||
if (value && value in defaultToolbarButtons) {
|
||||
let button = defaultToolbarButtons[value];
|
||||
this._addToolbarButton(
|
||||
button,
|
||||
place,
|
||||
(interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX !== undefined
|
||||
&& index
|
||||
=== interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the given button to the main (top) or extended (left) toolbar.
|
||||
*
|
||||
* @param {Object} the button to add.
|
||||
* @param {boolean} isFirst indicates if this is the first button in the
|
||||
* toolbar
|
||||
* @param {boolean} isLast indicates if this is the last button in the
|
||||
* toolbar
|
||||
* @param {boolean} isSplitter if this button is a splitter button for
|
||||
* the dialog, which means that a special splitter style will be applied
|
||||
*/
|
||||
_addToolbarButton(button, place, isSplitter) {
|
||||
const places = {
|
||||
main: 'mainToolbar',
|
||||
extended: 'extendedToolbarButtons'
|
||||
};
|
||||
let id = places[place];
|
||||
let buttonElement = document.createElement("a");
|
||||
if (button.className) {
|
||||
buttonElement.className = button.className;
|
||||
}
|
||||
|
||||
if (isSplitter) {
|
||||
let splitter = document.createElement('span');
|
||||
splitter.className = 'toolbar__splitter';
|
||||
document.getElementById(id).appendChild(splitter);
|
||||
}
|
||||
|
||||
buttonElement.id = button.id;
|
||||
|
||||
if (button.html)
|
||||
buttonElement.innerHTML = button.html;
|
||||
|
||||
//TODO: remove it after UI.updateDTMFSupport fix
|
||||
if (button.hidden)
|
||||
buttonElement.style.display = 'none';
|
||||
|
||||
if (button.shortcutAttr)
|
||||
buttonElement.setAttribute("shortcut", button.shortcutAttr);
|
||||
|
||||
if (button.content)
|
||||
buttonElement.setAttribute("content", button.content);
|
||||
|
||||
if (button.i18n)
|
||||
buttonElement.setAttribute("data-i18n", button.i18n);
|
||||
|
||||
buttonElement.setAttribute("data-container", "body");
|
||||
buttonElement.setAttribute("data-placement", "bottom");
|
||||
this._addPopups(buttonElement, button.popups);
|
||||
|
||||
document.getElementById(id)
|
||||
.appendChild(buttonElement);
|
||||
},
|
||||
|
||||
_addPopups(buttonElement, popups = []) {
|
||||
popups.forEach((popup) => {
|
||||
const popupElement = document.createElement('div');
|
||||
popupElement.id = popup.id;
|
||||
popupElement.className = popup.className;
|
||||
popupElement.setAttribute('data-i18n', popup.dataAttr);
|
||||
|
||||
let gravity = 'n';
|
||||
if (popup.dataAttrPosition)
|
||||
gravity = popup.dataAttrPosition;
|
||||
// use custom attribute to save gravity option
|
||||
// we use 'data-tooltip' in UIUtil to activate all tooltips
|
||||
// but we want these to be manually triggered
|
||||
popupElement.setAttribute('tooltip-gravity', gravity);
|
||||
|
||||
APP.translation.translateElement($(popupElement));
|
||||
|
||||
buttonElement.appendChild(popupElement);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show custom popup/tooltip for a specified button.
|
||||
* @param popupSelectorID the selector id of the popup to show
|
||||
* @param show true or false/show or hide the popup
|
||||
* @param timeout the time to show the popup
|
||||
*/
|
||||
_showCustomToolbarPopup(popupSelectorID, show, timeout) {
|
||||
|
||||
const gravity = $(popupSelectorID).attr('tooltip-gravity');
|
||||
AJS.$(popupSelectorID)
|
||||
.tooltip({
|
||||
trigger: 'manual',
|
||||
html: true,
|
||||
gravity: gravity,
|
||||
title: 'title'});
|
||||
if (show) {
|
||||
AJS.$(popupSelectorID).tooltip('show');
|
||||
setTimeout(function () {
|
||||
// hide the tooltip
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
}, timeout);
|
||||
} else {
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the toggled state of the given element depending on the isToggled
|
||||
* parameter.
|
||||
*
|
||||
* @param elementId the element identifier
|
||||
* @param isToggled indicates if the element should be toggled or untoggled
|
||||
*/
|
||||
_setToggledState(elementId, isToggled) {
|
||||
$("#" + elementId).toggleClass("toggled", isToggled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets Shortcuts and Tooltips for all toolbar buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setShortcutsAndTooltips() {
|
||||
Object.keys(defaultToolbarButtons).forEach(
|
||||
id => {
|
||||
if (UIUtil.isButtonEnabled(id)) {
|
||||
let button = defaultToolbarButtons[id];
|
||||
let buttonElement = document.getElementById(button.id);
|
||||
if (!buttonElement) return false;
|
||||
let tooltipPosition
|
||||
= (interfaceConfig.MAIN_TOOLBAR_BUTTONS
|
||||
.indexOf(id) > -1)
|
||||
? "bottom" : "right";
|
||||
|
||||
UIUtil.setTooltip( buttonElement,
|
||||
button.tooltipKey,
|
||||
tooltipPosition);
|
||||
|
||||
if (button.shortcut)
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
button.shortcut,
|
||||
button.shortcutAttr,
|
||||
button.shortcutFunc,
|
||||
button.shortcutDescription
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets Handlers for all toolbar buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setButtonHandlers() {
|
||||
Object.keys(buttonHandlers).forEach(
|
||||
buttonId => $(`#${buttonId}`).click(function(event) {
|
||||
!$(this).prop('disabled') && buttonHandlers[buttonId](event);
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Toolbar;
|
|
@ -1,149 +0,0 @@
|
|||
/* global APP, config, $, interfaceConfig */
|
||||
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import Toolbar from './Toolbar';
|
||||
import SideContainerToggler from "../side_pannels/SideContainerToggler";
|
||||
|
||||
let toolbarTimeoutObject;
|
||||
let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
/**
|
||||
* If true the toolbar will be always displayed
|
||||
*/
|
||||
let alwaysVisibleToolbar = false;
|
||||
|
||||
function showDesktopSharingButton() {
|
||||
if (APP.conference.isDesktopSharingEnabled &&
|
||||
UIUtil.isButtonEnabled('desktop')) {
|
||||
$('#toolbar_button_desktopsharing').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#toolbar_button_desktopsharing').css({display: "none"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the toolbar.
|
||||
*
|
||||
* @param force {true} to force the hiding of the toolbar without caring about
|
||||
* the extended toolbar side panels.
|
||||
*/
|
||||
function hideToolbar(force) { // eslint-disable-line no-unused-vars
|
||||
if (alwaysVisibleToolbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
|
||||
if (force !== true &&
|
||||
(Toolbar.isHovered()
|
||||
|| APP.UI.isRingOverlayVisible()
|
||||
|| SideContainerToggler.isVisible())) {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
} else {
|
||||
Toolbar.hide();
|
||||
$('#subject').animate({top: "-=40"}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
const ToolbarToggler = {
|
||||
/**
|
||||
* Initializes the ToolbarToggler
|
||||
*/
|
||||
init() {
|
||||
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
|
||||
|
||||
// disabled
|
||||
//this._registerWindowClickListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers click listeners handling the show and hode of toolbars when
|
||||
* user clicks outside of toolbar area.
|
||||
*/
|
||||
_registerWindowClickListeners() {
|
||||
$(window).click(function() {
|
||||
(Toolbar.isEnabled() && Toolbar.isVisible())
|
||||
? hideToolbar(true)
|
||||
: this.showToolbar();
|
||||
}.bind(this));
|
||||
|
||||
Toolbar.registerClickListeners(function(event){
|
||||
event.stopPropagation();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of alwaysVisibleToolbar variable.
|
||||
* @param value {boolean} the new value of alwaysVisibleToolbar variable
|
||||
*/
|
||||
setAlwaysVisibleToolbar(value) {
|
||||
alwaysVisibleToolbar = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the value of alwaysVisibleToolbar variable to the default one.
|
||||
*/
|
||||
resetAlwaysVisibleToolbar() {
|
||||
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the main toolbar.
|
||||
* @param timeout (optional) to specify custom timeout value
|
||||
*/
|
||||
showToolbar (timeout) {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
var updateTimeout = false;
|
||||
if (Toolbar.isEnabled() && !Toolbar.isVisible()) {
|
||||
Toolbar.show();
|
||||
$('#subject').animate({top: "+=40"}, 300);
|
||||
updateTimeout = true;
|
||||
}
|
||||
|
||||
if (updateTimeout) {
|
||||
if (toolbarTimeoutObject) {
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
}
|
||||
toolbarTimeoutObject
|
||||
= setTimeout(hideToolbar, timeout || toolbarTimeout);
|
||||
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
// Show/hide desktop sharing button
|
||||
showDesktopSharingButton();
|
||||
},
|
||||
|
||||
/**
|
||||
* Docks/undocks the toolbar.
|
||||
*
|
||||
* @param isDock indicates what operation to perform
|
||||
*/
|
||||
dockToolbar (isDock) {
|
||||
if (interfaceConfig.filmStripOnly || !Toolbar.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDock) {
|
||||
// First make sure the toolbar is shown.
|
||||
if (!Toolbar.isVisible()) {
|
||||
this.showToolbar();
|
||||
}
|
||||
|
||||
// Then clear the time out, to dock the toolbar.
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
} else {
|
||||
if (Toolbar.isVisible()) {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
} else {
|
||||
this.showToolbar();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ToolbarToggler;
|
|
@ -6,7 +6,7 @@ import { DialogContainer } from '../../base/dialog';
|
|||
import { Container } from '../../base/react';
|
||||
import { FilmStrip } from '../../film-strip';
|
||||
import { LargeVideo } from '../../large-video';
|
||||
import { Toolbar } from '../../toolbar';
|
||||
import { setToolbarVisible, Toolbar } from '../../toolbar';
|
||||
|
||||
import { styles } from './styles';
|
||||
|
||||
|
@ -28,8 +28,39 @@ class Conference extends Component {
|
|||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
dispatch: React.PropTypes.func
|
||||
}
|
||||
/**
|
||||
* The handler which dispatches the (redux) action connect.
|
||||
*
|
||||
* @private
|
||||
* @type {Function}
|
||||
*/
|
||||
_onConnect: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action disconnect.
|
||||
*
|
||||
* @private
|
||||
* @type {Function}
|
||||
*/
|
||||
_onDisconnect: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action setTooblarVisible to
|
||||
* show/hide the toolbar.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_setToolbarVisible: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether toolbar is visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_toolbarVisible: React.PropTypes.bool
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new Conference instance.
|
||||
|
@ -40,8 +71,6 @@ class Conference extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { toolbarVisible: true };
|
||||
|
||||
/**
|
||||
* The numerical ID of the timeout in milliseconds after which the
|
||||
* toolbar will be hidden. To be used with
|
||||
|
@ -62,7 +91,7 @@ class Conference extends Component {
|
|||
* returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._setToolbarTimeout(this.state.toolbarVisible);
|
||||
this._setToolbarTimeout(this.props._toolbarVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +101,7 @@ class Conference extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
componentWillMount() {
|
||||
this.props.dispatch(connect());
|
||||
this.props._onConnect();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,7 +114,7 @@ class Conference extends Component {
|
|||
componentWillUnmount() {
|
||||
this._clearToolbarTimeout();
|
||||
|
||||
this.props.dispatch(disconnect());
|
||||
this.props._onDisconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,8 +124,6 @@ class Conference extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const toolbarVisible = this.state.toolbarVisible;
|
||||
|
||||
return (
|
||||
<Container
|
||||
onClick = { this._onClick }
|
||||
|
@ -104,11 +131,11 @@ class Conference extends Component {
|
|||
touchFeedback = { false }>
|
||||
|
||||
<LargeVideo />
|
||||
<Toolbar visible = { toolbarVisible } />
|
||||
<FilmStrip visible = { !toolbarVisible } />
|
||||
|
||||
<Toolbar />
|
||||
<FilmStrip />
|
||||
|
||||
<DialogContainer />
|
||||
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -135,10 +162,9 @@ class Conference extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const toolbarVisible = !this.state.toolbarVisible;
|
||||
|
||||
this.setState({ toolbarVisible });
|
||||
const toolbarVisible = !this.props._toolbarVisible;
|
||||
|
||||
this.props._setToolbarVisible(toolbarVisible);
|
||||
this._setToolbarTimeout(toolbarVisible);
|
||||
}
|
||||
|
||||
|
@ -159,4 +185,73 @@ class Conference extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default reactReduxConnect()(Conference);
|
||||
/**
|
||||
* Maps dispatching of some action to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onConnect: Function,
|
||||
* _onDisconnect: Function,
|
||||
* _setToolbarVisible: Function
|
||||
* }}
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
/**
|
||||
* Dispatched an action connecting to the conference.
|
||||
*
|
||||
* @returns {Object} Dispatched action.
|
||||
* @private
|
||||
*/
|
||||
_onConnect() {
|
||||
return dispatch(connect());
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action disconnecting from the conference.
|
||||
*
|
||||
* @returns {Object} Dispatched action.
|
||||
* @private
|
||||
*/
|
||||
_onDisconnect() {
|
||||
return dispatch(disconnect());
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatched an action changing visiblity of the toolbar.
|
||||
*
|
||||
* @param {boolean} isVisible - Flag showing whether toolbar is
|
||||
* visible.
|
||||
* @returns {Object} Dispatched action.
|
||||
* @private
|
||||
*/
|
||||
_setToolbarVisible(isVisible: boolean) {
|
||||
return dispatch(setToolbarVisible(isVisible));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated Conference's props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _toolbarVisible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether toolbar is visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_toolbarVisible: state['features/toolbar'].visible
|
||||
};
|
||||
}
|
||||
|
||||
export default reactReduxConnect(_mapStateToProps, _mapDispatchToProps)(
|
||||
Conference);
|
||||
|
|
|
@ -6,9 +6,8 @@ import { connect as reactReduxConnect } from 'react-redux';
|
|||
import { connect, disconnect } from '../../base/connection';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { Watermarks } from '../../base/react';
|
||||
import { FeedbackButton } from '../../feedback';
|
||||
import { OverlayContainer } from '../../overlay';
|
||||
import { Notice } from '../../toolbar';
|
||||
import { Toolbar } from '../../toolbar';
|
||||
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
||||
|
||||
declare var $: Function;
|
||||
|
@ -66,25 +65,8 @@ class Conference extends Component {
|
|||
render() {
|
||||
return (
|
||||
<div id = 'videoconference_page'>
|
||||
<div id = 'mainToolbarContainer'>
|
||||
<Notice />
|
||||
<Toolbar />
|
||||
|
||||
<div
|
||||
className = 'toolbar'
|
||||
id = 'mainToolbar' />
|
||||
</div>
|
||||
<div
|
||||
className = 'hide'
|
||||
id = 'subject' />
|
||||
<div
|
||||
className = 'toolbar'
|
||||
id = 'extendedToolbar'>
|
||||
<div id = 'extendedToolbarButtons' />
|
||||
|
||||
<FeedbackButton />
|
||||
|
||||
<div id = 'sideToolbarContainer' />
|
||||
</div>
|
||||
<div id = 'videospace'>
|
||||
<div
|
||||
className = 'videocontainer'
|
||||
|
@ -154,6 +136,7 @@ class Conference extends Component {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogContainer />
|
||||
<OverlayContainer />
|
||||
<HideNotificationBarStyle />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Container } from '../../base/react';
|
||||
|
||||
|
@ -33,7 +33,7 @@ class FilmStrip extends Component {
|
|||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
visible: React.PropTypes.bool.isRequired
|
||||
_visible: React.PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ class FilmStrip extends Component {
|
|||
return (
|
||||
<Container
|
||||
style = { styles.filmStrip }
|
||||
visible = { this.props.visible }>
|
||||
visible = { this.props._visible }>
|
||||
<ScrollView
|
||||
|
||||
// eslint-disable-next-line react/jsx-curly-spacing
|
||||
|
@ -109,6 +109,7 @@ class FilmStrip extends Component {
|
|||
* @private
|
||||
* @returns {{
|
||||
* _participants: Participant[],
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
|
@ -119,7 +120,19 @@ function _mapStateToProps(state) {
|
|||
* @private
|
||||
* @type {Participant[]}
|
||||
*/
|
||||
_participants: state['features/base/participants']
|
||||
_participants: state['features/base/participants'],
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the film strip is visible.
|
||||
*
|
||||
* XXX The React Component FilmStrip is used on mobile only at the time
|
||||
* of this writing and on mobile the film strip is visible when the
|
||||
* toolbar is not.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: !state['features/toolbar'].visible
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import { Symbol } from '../base/react';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that toolbar timeout should be changed.
|
||||
*
|
||||
* {
|
||||
* type: CLEAR_TOOLBAR_TIMEOUT
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_TOOLBAR_TIMEOUT = Symbol('CLEAR_TOOLBAR_TIMEOUT');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a value for always visible toolbar
|
||||
* should be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_ALWAYS_VISIBLE_TOOLBAR,
|
||||
* alwaysVisible: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_ALWAYS_VISIBLE_TOOLBAR = Symbol('SET_ALWAYS_VISIBLE_TOOLBAR');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a value for conference subject
|
||||
* should be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_SUBJECT,
|
||||
* subject: string
|
||||
* }
|
||||
*/
|
||||
export const SET_SUBJECT = Symbol('SET_SUBJECT');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a value of subject slide in should
|
||||
* be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_SUBJECT_SLIDE_IN,
|
||||
* subjectSlideIn: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_SUBJECT_SLIDE_IN = Symbol('SET_SUBJECT_SLIDE_IN');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a value for toolbar button should
|
||||
* be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBAR_BUTTON,
|
||||
* button: Object,
|
||||
* key: string
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBAR_BUTTON = Symbol('SET_TOOLBAR_BUTTON');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that toolbar is/isn't being hovered.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBAR_HOVERED,
|
||||
* hovered: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBAR_HOVERED = Symbol('SET_TOOLBAR_HOVERED');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that new toolbar timeout should be set
|
||||
* and the value of toolbar timeout should be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBAR_TIMEOUT,
|
||||
* handler: Function,
|
||||
toolbarTimeout: number
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBAR_TIMEOUT = Symbol('SET_TOOLBAR_TIMEOUT');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that value of toolbar timeout should
|
||||
* be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBAR_TIMEOUT_NUMBER,
|
||||
* toolbarTimeout: number
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBAR_TIMEOUT_NUMBER = Symbol('SET_TOOLBAR_TIMEOUT');
|
||||
|
||||
/**
|
||||
* The type of the (redux) action which shows/hides the toolbar.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBAR_VISIBLE,
|
||||
* visible: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBAR_VISIBLE = Symbol('SET_TOOLBAR_VISIBLE');
|
|
@ -0,0 +1,277 @@
|
|||
/* @flow */
|
||||
|
||||
import type { Dispatch } from 'redux-thunk';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBAR_TIMEOUT,
|
||||
SET_ALWAYS_VISIBLE_TOOLBAR,
|
||||
SET_SUBJECT,
|
||||
SET_SUBJECT_SLIDE_IN,
|
||||
SET_TOOLBAR_BUTTON,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBAR_TIMEOUT,
|
||||
SET_TOOLBAR_TIMEOUT_NUMBER,
|
||||
SET_TOOLBAR_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Event handler for local raise hand changed event.
|
||||
*
|
||||
* @param {boolean} handRaised - Flag showing whether hand is raised.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function changeLocalRaiseHand(handRaised: boolean): Function {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { secondaryToolbarButtons } = state['features/toolbar'];
|
||||
const buttonName = 'raisehand';
|
||||
const button = secondaryToolbarButtons.get(buttonName);
|
||||
|
||||
button.toggled = handRaised;
|
||||
|
||||
dispatch(setToolbarButton(buttonName, button));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbar timeout should be cleared.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLEAR_TOOLBAR_TIMEOUT
|
||||
* }}
|
||||
*/
|
||||
export function clearToolbarTimeout(): Object {
|
||||
return {
|
||||
type: CLEAR_TOOLBAR_TIMEOUT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that always visible toolbars value should be changed.
|
||||
*
|
||||
* @param {boolean} alwaysVisible - Value to be set in redux store.
|
||||
* @returns {{
|
||||
* type: SET_ALWAYS_VISIBLE_TOOLBAR,
|
||||
* alwaysVisible: bool
|
||||
* }}
|
||||
*/
|
||||
export function setAlwaysVisibleToolbar(alwaysVisible: boolean): Object {
|
||||
return {
|
||||
type: SET_ALWAYS_VISIBLE_TOOLBAR,
|
||||
alwaysVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled - Indicates if the button should be enabled
|
||||
* or disabled.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setAudioIconEnabled(enabled: boolean = false): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const i18nKey = enabled ? 'mute' : 'micDisabled';
|
||||
const i18n = `[content]toolbar.${i18nKey}`;
|
||||
const button = {
|
||||
enabled,
|
||||
i18n,
|
||||
toggled: !enabled
|
||||
};
|
||||
|
||||
dispatch(setToolbarButton('microphone', button));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that value of conference subject should be changed.
|
||||
*
|
||||
* @param {string} subject - Conference subject string.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setSubject(subject: string) {
|
||||
return {
|
||||
type: SET_SUBJECT,
|
||||
subject
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbar subject slide in value should be changed.
|
||||
*
|
||||
* @param {boolean} subjectSlideIn - Flag showing whether subject is shown.
|
||||
* @returns {{
|
||||
* type: SET_SUBJECT_SLIDE_IN,
|
||||
* subjectSlideIn: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setSubjectSlideIn(subjectSlideIn: boolean): Object {
|
||||
return {
|
||||
type: SET_SUBJECT_SLIDE_IN,
|
||||
subjectSlideIn
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that value of the button specified by key should be changed.
|
||||
*
|
||||
* @param {string} buttonName - Button key.
|
||||
* @param {Object} button - Button object.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_BUTTON,
|
||||
* buttonName: string,
|
||||
* button: Object
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarButton(buttonName: string, button: Object): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_BUTTON,
|
||||
buttonName,
|
||||
button
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbar is hovered value should be changed.
|
||||
*
|
||||
* @param {boolean} hovered - Flag showing whether toolbar is hovered.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_HOVERED,
|
||||
* hovered: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarHovered(hovered: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_HOVERED,
|
||||
hovered
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action which sets new timeout and clears the previous one.
|
||||
*
|
||||
* @param {Function} handler - Function to be invoked after the timeout.
|
||||
* @param {number} toolbarTimeout - Delay.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_TIMEOUT,
|
||||
* handler: Function,
|
||||
* toolbarTimeout: number
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarTimeout(handler: Function,
|
||||
toolbarTimeout: number): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_TIMEOUT,
|
||||
handler,
|
||||
toolbarTimeout
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action which sets new toolbar timeout value.
|
||||
*
|
||||
* @param {number} toolbarTimeout - Delay.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_TIMEOUT_NUMBER,
|
||||
* toolbarTimeout: number
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarTimeoutNumber(toolbarTimeout: number): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_TIMEOUT_NUMBER,
|
||||
toolbarTimeout
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the toolbar.
|
||||
*
|
||||
* @param {boolean} visible - True to show the toolbar or false to hide it.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_VISIBLE,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarVisible(visible: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled - Indicates if the button should be enabled
|
||||
* or disabled.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setVideoIconEnabled(enabled: boolean = false): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const i18nKey = enabled ? 'videomute' : 'cameraDisabled';
|
||||
const i18n = `[content]toolbar.${i18nKey}`;
|
||||
const button = {
|
||||
enabled,
|
||||
i18n,
|
||||
toggled: !enabled
|
||||
};
|
||||
|
||||
dispatch(setToolbarButton('camera', button));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows etherpad button if it's not shown.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showEtherpadButton(): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
dispatch(setToolbarButton('etherpad', {
|
||||
hidden: false
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for full screen toggled event.
|
||||
*
|
||||
* @param {boolean} isFullScreen - Flag showing whether app in full
|
||||
* screen mode.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleFullScreen(isFullScreen: boolean): Function {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { primaryToolbarButtons } = state['features/toolbar'];
|
||||
const buttonName = 'fullscreen';
|
||||
const button = primaryToolbarButtons.get(buttonName);
|
||||
|
||||
button.toggled = isFullScreen;
|
||||
|
||||
dispatch(setToolbarButton(buttonName, button));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets negation of button's toggle property.
|
||||
*
|
||||
* @param {string} buttonName - Button key.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleToolbarButton(buttonName: string): Function {
|
||||
return (dispatch: Dispatch, getState: Function) => {
|
||||
const state = getState();
|
||||
const {
|
||||
primaryToolbarButtons,
|
||||
secondaryToolbarButtons
|
||||
} = state['features/toolbar'];
|
||||
const button
|
||||
= primaryToolbarButtons.get(buttonName)
|
||||
|| secondaryToolbarButtons.get(buttonName);
|
||||
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
toggled: !button.toggled
|
||||
}));
|
||||
};
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/* @flow */
|
||||
|
||||
import Recording from '../../../modules/UI/recording/Recording';
|
||||
import SideContainerToggler
|
||||
from '../../../modules/UI/side_pannels/SideContainerToggler';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import UIUtil from '../../../modules/UI/util/UIUtil';
|
||||
|
||||
import {
|
||||
clearToolbarTimeout,
|
||||
setAlwaysVisibleToolbar,
|
||||
setSubjectSlideIn,
|
||||
setToolbarButton,
|
||||
setToolbarTimeout,
|
||||
setToolbarTimeoutNumber,
|
||||
setToolbarVisible,
|
||||
toggleToolbarButton
|
||||
} from './actions.native';
|
||||
|
||||
export * from './actions.native';
|
||||
|
||||
declare var $: Function;
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Checks whether desktop sharing is enabled and whether
|
||||
* we have params to start automatically sharing.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function checkAutoEnableDesktopSharing(): Function {
|
||||
return () => {
|
||||
// XXX Should use dispatcher to toggle screensharing but screensharing
|
||||
// hasn't been React-ified yet.
|
||||
|
||||
if (UIUtil.isButtonEnabled('desktop')
|
||||
&& config.autoEnableDesktopSharing) {
|
||||
APP.UI.eventEmitter.emit(UIEvents.TOGGLE_SCREENSHARING);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Docks/undocks toolbar based on its parameter.
|
||||
*
|
||||
* @param {boolean} dock - True if dock, false otherwise.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function dockToolbar(dock: boolean): Function {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const { toolbarTimeout, visible } = state['features/toolbar'];
|
||||
|
||||
if (dock) {
|
||||
// First make sure the toolbar is shown.
|
||||
visible || dispatch(showToolbar());
|
||||
|
||||
dispatch(clearToolbarTimeout());
|
||||
} else if (visible) {
|
||||
dispatch(
|
||||
setToolbarTimeout(
|
||||
() => dispatch(hideToolbar()),
|
||||
toolbarTimeout));
|
||||
} else {
|
||||
dispatch(showToolbar());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the toolbar.
|
||||
*
|
||||
* @param {boolean} force - True to force the hiding of the toolbar without
|
||||
* caring about the extended toolbar side panels.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hideToolbar(force: boolean = false): Function {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const {
|
||||
alwaysVisible,
|
||||
hovered,
|
||||
toolbarTimeout
|
||||
} = state['features/toolbar'];
|
||||
|
||||
if (alwaysVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(clearToolbarTimeout());
|
||||
|
||||
if (!force
|
||||
&& (hovered
|
||||
|| APP.UI.isRingOverlayVisible()
|
||||
|| SideContainerToggler.isVisible())) {
|
||||
dispatch(
|
||||
setToolbarTimeout(
|
||||
() => dispatch(hideToolbar()),
|
||||
toolbarTimeout));
|
||||
} else {
|
||||
dispatch(setToolbarVisible(false));
|
||||
dispatch(setSubjectSlideIn(false));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that reset always visible toolbar to default state.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function resetAlwaysVisibleToolbar(): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const alwaysVisible = config.alwaysVisibleToolbar === true;
|
||||
|
||||
dispatch(setAlwaysVisibleToolbar(alwaysVisible));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that unclickable property of profile button should change its value.
|
||||
*
|
||||
* @param {boolean} unclickable - Shows whether button is unclickable.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setProfileButtonUnclickable(unclickable: boolean): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const buttonName = 'profile';
|
||||
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
unclickable
|
||||
}));
|
||||
|
||||
UIUtil.removeTooltip(document.getElementById('toolbar_button_profile'));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows desktop sharing button.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showDesktopSharingButton(): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const buttonName = 'desktop';
|
||||
const visible
|
||||
= APP.conference.isDesktopSharingEnabled
|
||||
&& UIUtil.isButtonEnabled(buttonName);
|
||||
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
hidden: !visible
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the dialpad button.
|
||||
*
|
||||
* @param {boolean} show - Flag showing whether to show button or not.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showDialPadButton(show: boolean): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const buttonName = 'dialpad';
|
||||
const shouldShow = UIUtil.isButtonEnabled(buttonName) && show;
|
||||
|
||||
if (shouldShow) {
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
hidden: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows recording button.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showRecordingButton(): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const eventEmitter = APP.UI.eventEmitter;
|
||||
const buttonName = 'recording';
|
||||
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
hidden: false
|
||||
}));
|
||||
|
||||
Recording.init(eventEmitter, config.recordingType);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the 'shared video' button.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showSharedVideoButton(): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const buttonName = 'sharedvideo';
|
||||
const shouldShow
|
||||
= UIUtil.isButtonEnabled(buttonName)
|
||||
&& !config.disableThirdPartyRequests;
|
||||
|
||||
if (shouldShow) {
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
hidden: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows SIP call button if it's required and appropriate
|
||||
* flag is passed.
|
||||
*
|
||||
* @param {boolean} show - Flag showing whether to show button or not.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showSIPCallButton(show: boolean): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const buttonName = 'sip';
|
||||
const shouldShow
|
||||
= APP.conference.sipGatewayEnabled()
|
||||
&& UIUtil.isButtonEnabled(buttonName)
|
||||
&& show;
|
||||
|
||||
if (shouldShow) {
|
||||
dispatch(setToolbarButton(buttonName, {
|
||||
hidden: !shouldShow
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the toolbar for specified timeout.
|
||||
*
|
||||
* @param {number} timeout - Timeout for showing the toolbar.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showToolbar(timeout: number = 0): Object {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const { toolbarTimeout, visible } = state['features/toolbar'];
|
||||
const finalTimeout = timeout || toolbarTimeout;
|
||||
|
||||
if (!visible) {
|
||||
dispatch(setToolbarVisible(true));
|
||||
dispatch(setSubjectSlideIn(true));
|
||||
dispatch(
|
||||
setToolbarTimeout(() => dispatch(hideToolbar()), finalTimeout));
|
||||
dispatch(setToolbarTimeoutNumber(interfaceConfig.TOOLBAR_TIMEOUT));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for side toolbar container toggled event.
|
||||
*
|
||||
* @param {string} containerId - ID of the container.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function toggleSideToolbarContainer(containerId: string): Function {
|
||||
return (dispatch: Dispatch, getState: Function) => {
|
||||
const state = getState();
|
||||
const { secondaryToolbarButtons } = state['features/toolbar'];
|
||||
|
||||
for (const key of secondaryToolbarButtons.keys()) {
|
||||
const isButtonEnabled = UIUtil.isButtonEnabled(key);
|
||||
const button = secondaryToolbarButtons.get(key);
|
||||
|
||||
if (isButtonEnabled
|
||||
&& button.sideContainerId
|
||||
&& button.sideContainerId === containerId) {
|
||||
dispatch(toggleToolbarButton(key));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
import { toggleAudioMuted, toggleVideoMuted } from '../../base/media';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { beginRoomLockRequest } from '../../room-lock';
|
||||
|
||||
import { styles } from './styles';
|
||||
|
||||
/**
|
||||
* Abstract (base) class for the conference toolbar.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export class AbstractToolbar extends Component {
|
||||
/**
|
||||
* AbstractToolbar component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
_audioMuted: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is
|
||||
* locked/password-protected.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_locked: React.PropTypes.bool,
|
||||
_videoMuted: React.PropTypes.bool,
|
||||
dispatch: React.PropTypes.func,
|
||||
visible: React.PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new AbstractToolbar instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onHangup = this._onHangup.bind(this);
|
||||
this._onRoomLock = this._onRoomLock.bind(this);
|
||||
this._toggleAudio = this._toggleAudio.bind(this);
|
||||
this._toggleVideo = this._toggleVideo.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the styles for a button that toggles the mute state of a specific
|
||||
* media type.
|
||||
*
|
||||
* @param {string} mediaType - The {@link MEDIA_TYPE} associated with the
|
||||
* button to get styles for.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* iconName: string,
|
||||
* iconStyle: Object,
|
||||
* style: Object
|
||||
* }}
|
||||
*/
|
||||
_getMuteButtonStyles(mediaType) {
|
||||
let iconName;
|
||||
let iconStyle;
|
||||
let style = styles.primaryToolbarButton;
|
||||
|
||||
if (this.props[`_${mediaType}Muted`]) {
|
||||
iconName = this[`${mediaType}MutedIcon`];
|
||||
iconStyle = styles.whiteIcon;
|
||||
style = {
|
||||
...style,
|
||||
backgroundColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
} else {
|
||||
iconName = this[`${mediaType}Icon`];
|
||||
iconStyle = styles.icon;
|
||||
}
|
||||
|
||||
return {
|
||||
iconName,
|
||||
iconStyle,
|
||||
style
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches action to leave the current conference.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onHangup() {
|
||||
// XXX We don't know here which value is effectively/internally used
|
||||
// when there's no valid room name to join. It isn't our business to
|
||||
// know that anyway. The undefined value is our expression of (1) the
|
||||
// lack of knowledge & (2) the desire to no longer have a valid room
|
||||
// name to join.
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to set the lock i.e. password protection of the
|
||||
* conference/room.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onRoomLock() {
|
||||
this.props.dispatch(beginRoomLockRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the audio/microphone.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleAudio() {
|
||||
this.props.dispatch(toggleAudioMuted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the video/camera.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleVideo() {
|
||||
this.props.dispatch(toggleVideoMuted());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps parts of media state to component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* _audioMuted: boolean,
|
||||
* _locked: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state) {
|
||||
const conference = state['features/base/conference'];
|
||||
const media = state['features/base/media'];
|
||||
|
||||
return {
|
||||
_audioMuted: media.audio.muted,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is
|
||||
* locked/password-protected.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_locked: conference.locked,
|
||||
_videoMuted: media.video.muted
|
||||
};
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
setToolbarHovered
|
||||
} from '../actions';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Class implementing Primary Toolbar React component.
|
||||
*
|
||||
* @class PrimaryToolbar
|
||||
* @extends Component
|
||||
*/
|
||||
class BaseToolbar extends Component {
|
||||
|
||||
_renderToolbarButton: Function;
|
||||
|
||||
/**
|
||||
* Base toolbar component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Handler for mouse out event.
|
||||
*/
|
||||
_onMouseOut: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for mouse over event.
|
||||
*/
|
||||
_onMouseOver: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Contains button handlers.
|
||||
*/
|
||||
buttonHandlers: React.PropTypes.object,
|
||||
|
||||
/**
|
||||
* Children of current React component.
|
||||
*/
|
||||
children: React.PropTypes.element,
|
||||
|
||||
/**
|
||||
* Toolbar's class name.
|
||||
*/
|
||||
className: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* If the toolbar requires splitter this property defines splitter
|
||||
* index.
|
||||
*/
|
||||
splitterIndex: React.PropTypes.number,
|
||||
|
||||
/**
|
||||
* Map with toolbar buttons.
|
||||
*/
|
||||
toolbarButtons: React.PropTypes.instanceOf(Map)
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor of Primary toolbar class.
|
||||
*
|
||||
* @param {Object} props - Object containing React component properties.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._setButtonHandlers();
|
||||
|
||||
// Bind methods to save the context
|
||||
this._renderToolbarButton = this._renderToolbarButton.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render(): ReactElement<*> {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { `toolbar ${className}` }
|
||||
onMouseOut = { this.props._onMouseOut }
|
||||
onMouseOver = { this.props._onMouseOver }>
|
||||
{
|
||||
[ ...this.props.toolbarButtons.entries() ]
|
||||
.reduce(this._renderToolbarButton, [])
|
||||
}
|
||||
{
|
||||
this.props.children
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders toolbar button. Method is passed to reduce function.
|
||||
*
|
||||
* @param {Array} acc - Toolbar buttons array.
|
||||
* @param {Array} keyValuePair - Key value pair containing button and its
|
||||
* key.
|
||||
* @param {number} index - Index of the key value pair in the array.
|
||||
* @returns {Array} Array of toolbar buttons and splitter if it's on.
|
||||
* @private
|
||||
*/
|
||||
_renderToolbarButton(acc: Array<*>, keyValuePair: Array<*>,
|
||||
index: number): Array<ReactElement<*>> {
|
||||
const [ key, button ] = keyValuePair;
|
||||
const { splitterIndex } = this.props;
|
||||
|
||||
if (splitterIndex && index === splitterIndex) {
|
||||
const splitter = <span className = 'toolbar__splitter' />;
|
||||
|
||||
acc.push(splitter);
|
||||
}
|
||||
|
||||
const { onClick, onMount, onUnmount } = button;
|
||||
|
||||
acc.push(
|
||||
<ToolbarButton
|
||||
button = { button }
|
||||
key = { key }
|
||||
onClick = { onClick }
|
||||
onMount = { onMount }
|
||||
onUnmount = { onUnmount } />
|
||||
);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets handlers for some of the buttons.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setButtonHandlers(): void {
|
||||
const {
|
||||
buttonHandlers,
|
||||
toolbarButtons
|
||||
} = this.props;
|
||||
|
||||
Object.keys(buttonHandlers).forEach(key => {
|
||||
let button = toolbarButtons.get(key);
|
||||
|
||||
if (button) {
|
||||
button = {
|
||||
...button,
|
||||
...buttonHandlers[key]
|
||||
};
|
||||
toolbarButtons.set(key, button);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of Redux actions to component's props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Function): Object {
|
||||
return {
|
||||
/**
|
||||
* Dispatches an action signalling that toolbar is no being hovered.
|
||||
*
|
||||
* @protected
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onMouseOut() {
|
||||
return dispatch(setToolbarHovered(false));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action signalling that toolbar is now being hovered.
|
||||
*
|
||||
* @protected
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onMouseOver() {
|
||||
return dispatch(setToolbarHovered(true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, _mapDispatchToProps)(BaseToolbar);
|
|
@ -0,0 +1,189 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { showDesktopSharingButton, toggleFullScreen } from '../actions';
|
||||
import BaseToolbar from './BaseToolbar';
|
||||
import { getToolbarClassNames } from '../functions';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Implementation of PrimaryToolbar React Component.
|
||||
*
|
||||
* @class PrimaryToolbar
|
||||
* @extends Component
|
||||
*/
|
||||
class PrimaryToolbar extends Component {
|
||||
|
||||
state: Object;
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Handler for toggling fullscreen mode.
|
||||
*/
|
||||
_onFullScreenToggled: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for showing desktop sharing button.
|
||||
*/
|
||||
_onShowDesktopSharingButton: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Contains toolbar buttons for primary toolbar.
|
||||
*/
|
||||
_primaryToolbarButtons: React.PropTypes.instanceOf(Map),
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
*/
|
||||
_visible: React.PropTypes.bool
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs instance of primary toolbar React component.
|
||||
*
|
||||
* @param {Object} props - React component's properties.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const buttonHandlers = {
|
||||
/**
|
||||
* Mount handler for desktop button.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
desktop: {
|
||||
onMount: () => this.props._onShowDesktopSharingButton()
|
||||
},
|
||||
|
||||
/**
|
||||
* Mount/Unmount handler for toggling fullscreen button.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
fullscreen: {
|
||||
onMount: () =>
|
||||
APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED,
|
||||
this.props._onFullScreenToggled),
|
||||
onUnmount: () =>
|
||||
APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
|
||||
this.props._onFullScreenToggled)
|
||||
}
|
||||
};
|
||||
const splitterIndex = interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX;
|
||||
|
||||
this.state = {
|
||||
|
||||
/**
|
||||
* Object containing on mount/unmount handlers for toolbar buttons.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
buttonHandlers,
|
||||
|
||||
/**
|
||||
* If deployment supports toolbar splitter this value contains its
|
||||
* index.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
splitterIndex
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders primary toolbar component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { buttonHandlers, splitterIndex } = this.state;
|
||||
const { _primaryToolbarButtons } = this.props;
|
||||
const { primaryToolbarClassName } = getToolbarClassNames(this.props);
|
||||
|
||||
return (
|
||||
<BaseToolbar
|
||||
buttonHandlers = { buttonHandlers }
|
||||
className = { primaryToolbarClassName }
|
||||
splitterIndex = { splitterIndex }
|
||||
toolbarButtons = { _primaryToolbarButtons } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps some of the Redux actions to the component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {{
|
||||
* _onShowDesktopSharingButton: Function
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Function): Object {
|
||||
return {
|
||||
/**
|
||||
* Dispatches an action signalling that full screen mode is toggled.
|
||||
*
|
||||
* @param {boolean} isFullScreen - Show whether fullscreen mode is on.
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onFullScreenToggled(isFullScreen: boolean) {
|
||||
return dispatch(toggleFullScreen(isFullScreen));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action signalling that desktop sharing button
|
||||
* should be shown.
|
||||
*
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onShowDesktopSharingButton() {
|
||||
dispatch(showDesktopSharingButton());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of Redux store to React component props.
|
||||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _primaryToolbarButtons: Map,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const {
|
||||
primaryToolbarButtons,
|
||||
visible
|
||||
} = state['features/toolbar'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* Default toolbar buttons for primary toolbar.
|
||||
*
|
||||
* @protected
|
||||
* @type {Map}
|
||||
*/
|
||||
_primaryToolbarButtons: primaryToolbarButtons,
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: visible
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(PrimaryToolbar);
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { FeedbackButton } from '../../feedback';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
changeLocalRaiseHand,
|
||||
setProfileButtonUnclickable,
|
||||
showRecordingButton,
|
||||
toggleSideToolbarContainer
|
||||
} from '../actions';
|
||||
import BaseToolbar from './BaseToolbar';
|
||||
import { getToolbarClassNames } from '../functions';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
|
||||
/**
|
||||
* Implementation of secondary toolbar React component.
|
||||
*
|
||||
* @class SecondaryToolbar
|
||||
* @extends Component
|
||||
*/
|
||||
class SecondaryToolbar extends Component {
|
||||
|
||||
state: Object;
|
||||
|
||||
/**
|
||||
* Secondary toolbar property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Handler dispatching local "Raise hand".
|
||||
*/
|
||||
_onLocalRaiseHandChanged: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler setting profile button unclickable.
|
||||
*/
|
||||
_onSetProfileButtonUnclickable: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for showing recording button.
|
||||
*/
|
||||
_onShowRecordingButton: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler dispatching toggle toolbar container.
|
||||
*/
|
||||
_onSideToolbarContainerToggled: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Contains map of secondary toolbar buttons.
|
||||
*/
|
||||
_secondaryToolbarButtons: React.PropTypes.instanceOf(Map),
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
*/
|
||||
_visible: React.PropTypes.bool
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs instance of SecondaryToolbar component.
|
||||
*
|
||||
* @param {Object} props - React component properties.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const buttonHandlers = {
|
||||
/**
|
||||
* Mount handler for profile button.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
profile: {
|
||||
onMount: () => {
|
||||
APP.tokenData.isGuest
|
||||
|| this.props._onSetProfileButtonUnclickable(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Mount/Unmount handlers for raisehand button.
|
||||
*
|
||||
* @type {button}
|
||||
*/
|
||||
raisehand: {
|
||||
onMount: () => {
|
||||
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
this.props._onLocalRaiseHandChanged);
|
||||
},
|
||||
onUnmount: () => {
|
||||
APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
this.props._onLocalRaiseHandChanged);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Mount handler for recording button.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
recording: {
|
||||
onMount: () => {
|
||||
if (config.enableRecording) {
|
||||
this.props._onShowRecordingButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* Object containing on mount/unmount handlers for toolbar buttons.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
buttonHandlers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register legacy UI listener.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount(): void {
|
||||
APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
this.props._onSideToolbarContainerToggled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters legacy UI listener.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount(): void {
|
||||
APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
this.props._onSideToolbarContainerToggled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders secondary toolbar component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render(): ReactElement<*> {
|
||||
const { buttonHandlers } = this.state;
|
||||
const { _secondaryToolbarButtons } = this.props;
|
||||
const { secondaryToolbarClassName } = getToolbarClassNames(this.props);
|
||||
|
||||
return (
|
||||
<BaseToolbar
|
||||
buttonHandlers = { buttonHandlers }
|
||||
className = { secondaryToolbarClassName }
|
||||
toolbarButtons = { _secondaryToolbarButtons }>
|
||||
<FeedbackButton />
|
||||
</BaseToolbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps some of Redux actions to component's props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {{
|
||||
* _onLocalRaiseHandChanged: Function,
|
||||
* _onSetProfileButtonUnclickable: Function,
|
||||
* _onShowRecordingButton: Function,
|
||||
* _onSideToolbarContainerToggled
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Function): Object {
|
||||
return {
|
||||
/**
|
||||
* Dispatches an action that 'hand' is raised.
|
||||
*
|
||||
* @param {boolean} isRaisedHand - Show whether hand is raised.
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onLocalRaiseHandChanged(isRaisedHand: boolean) {
|
||||
return dispatch(changeLocalRaiseHand(isRaisedHand));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action signalling to set profile button unclickable.
|
||||
*
|
||||
* @param {boolean} unclickable - Flag showing whether unclickable
|
||||
* property is true.
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onSetProfileButtonUnclickable(unclickable: boolean) {
|
||||
return dispatch(setProfileButtonUnclickable(unclickable));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action signalling that recording button should be
|
||||
* shown.
|
||||
*
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onShowRecordingButton() {
|
||||
return dispatch(showRecordingButton());
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action signalling that side toolbar container is
|
||||
* toggled.
|
||||
*
|
||||
* @param {string} containerId - Id of side toolbar container.
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onSideToolbarContainerToggled(containerId: string) {
|
||||
return dispatch(toggleSideToolbarContainer(containerId));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of Redux state to component's props.
|
||||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _secondaryToolbarButtons: Map,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const {
|
||||
secondaryToolbarButtons,
|
||||
visible
|
||||
} = state['features/toolbar'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* Default toolbar buttons for secondary toolbar.
|
||||
*
|
||||
* @protected
|
||||
* @type {Map}
|
||||
*/
|
||||
_secondaryToolbarButtons: secondaryToolbarButtons,
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: visible
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(SecondaryToolbar);
|
|
@ -1,41 +1,74 @@
|
|||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
|
||||
import { Container } from '../../base/react';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { beginRoomLockRequest } from '../../room-lock';
|
||||
|
||||
import { AbstractToolbar, _mapStateToProps } from './AbstractToolbar';
|
||||
import {
|
||||
abstractMapDispatchToProps,
|
||||
abstractMapStateToProps
|
||||
} from '../functions';
|
||||
import { styles } from './styles';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
|
||||
/**
|
||||
* Implements the conference toolbar on React Native.
|
||||
*
|
||||
* @extends AbstractToolbar
|
||||
*/
|
||||
class Toolbar extends AbstractToolbar {
|
||||
class Toolbar extends Component {
|
||||
/**
|
||||
* Toolbar component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = AbstractToolbar.propTypes
|
||||
static propTypes = {
|
||||
/**
|
||||
* Flag showing that audio is muted.
|
||||
*/
|
||||
_audioMuted: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Initializes a new Toolbar instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
* Flag showing whether room is locked.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
_locked: React.PropTypes.bool,
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._toggleCameraFacingMode
|
||||
= this._toggleCameraFacingMode.bind(this);
|
||||
}
|
||||
/**
|
||||
* Handler for hangup.
|
||||
*/
|
||||
_onHangup: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for room locking.
|
||||
*/
|
||||
_onRoomLock: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for toggle audio.
|
||||
*/
|
||||
_onToggleAudio: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for toggling camera facing mode.
|
||||
*/
|
||||
_onToggleCameraFacingMode: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for toggling video.
|
||||
*/
|
||||
_onToggleVideo: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Flag showing whether video is muted.
|
||||
*/
|
||||
_videoMuted: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Flag showing whether toolbar is visible.
|
||||
*/
|
||||
_visible: React.PropTypes.bool
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -47,7 +80,7 @@ class Toolbar extends AbstractToolbar {
|
|||
return (
|
||||
<Container
|
||||
style = { styles.toolbarContainer }
|
||||
visible = { this.props.visible }>
|
||||
visible = { this.props._visible }>
|
||||
{
|
||||
this._renderPrimaryToolbar()
|
||||
}
|
||||
|
@ -58,6 +91,43 @@ class Toolbar extends AbstractToolbar {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the styles for a button that toggles the mute state of a specific
|
||||
* media type.
|
||||
*
|
||||
* @param {string} mediaType - The {@link MEDIA_TYPE} associated with the
|
||||
* button to get styles for.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* iconName: string,
|
||||
* iconStyle: Object,
|
||||
* style: Object
|
||||
* }}
|
||||
*/
|
||||
_getMuteButtonStyles(mediaType) {
|
||||
let iconName;
|
||||
let iconStyle;
|
||||
let style = styles.primaryToolbarButton;
|
||||
|
||||
if (this.props[`_${mediaType}Muted`]) {
|
||||
iconName = this[`${mediaType}MutedIcon`];
|
||||
iconStyle = styles.whiteIcon;
|
||||
style = {
|
||||
...style,
|
||||
backgroundColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
} else {
|
||||
iconName = this[`${mediaType}Icon`];
|
||||
iconStyle = styles.icon;
|
||||
}
|
||||
|
||||
return {
|
||||
iconName,
|
||||
iconStyle,
|
||||
style
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the toolbar which contains the primary buttons such as hangup,
|
||||
* audio and video mute.
|
||||
|
@ -76,12 +146,12 @@ class Toolbar extends AbstractToolbar {
|
|||
<ToolbarButton
|
||||
iconName = { audioButtonStyles.iconName }
|
||||
iconStyle = { audioButtonStyles.iconStyle }
|
||||
onClick = { this._toggleAudio }
|
||||
onClick = { this.props._onToggleAudio }
|
||||
style = { audioButtonStyles.style } />
|
||||
<ToolbarButton
|
||||
iconName = 'hangup'
|
||||
iconStyle = { styles.whiteIcon }
|
||||
onClick = { this._onHangup }
|
||||
onClick = { this.props._onHangup }
|
||||
style = {{
|
||||
...styles.primaryToolbarButton,
|
||||
backgroundColor: ColorPalette.red
|
||||
|
@ -90,7 +160,7 @@ class Toolbar extends AbstractToolbar {
|
|||
<ToolbarButton
|
||||
iconName = { videoButtonStyles.iconName }
|
||||
iconStyle = { videoButtonStyles.iconStyle }
|
||||
onClick = { this._toggleVideo }
|
||||
onClick = { this.props._onToggleVideo }
|
||||
style = { videoButtonStyles.style } />
|
||||
</View>
|
||||
);
|
||||
|
@ -125,7 +195,7 @@ class Toolbar extends AbstractToolbar {
|
|||
<ToolbarButton
|
||||
iconName = 'switch-camera'
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this._toggleCameraFacingMode }
|
||||
onClick = { this.props._onToggleCameraFacingMode }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
*/}
|
||||
|
@ -134,7 +204,7 @@ class Toolbar extends AbstractToolbar {
|
|||
this.props._locked ? 'security-locked' : 'security'
|
||||
}
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this._onRoomLock }
|
||||
onClick = { this.props._onRoomLock }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
</View>
|
||||
|
@ -142,17 +212,6 @@ class Toolbar extends AbstractToolbar {
|
|||
|
||||
/* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches between the front/user-facing and rear/environment-facing
|
||||
* cameras.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleCameraFacingMode() {
|
||||
this.props.dispatch(toggleCameraFacingMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,4 +227,70 @@ Object.assign(Toolbar.prototype, {
|
|||
videoMutedIcon: 'camera-disabled'
|
||||
});
|
||||
|
||||
export default connect(_mapStateToProps)(Toolbar);
|
||||
/**
|
||||
* Maps actions to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {{
|
||||
* _onRoomLock: Function,
|
||||
* _onToggleCameraFacingMode: Function,
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
...abstractMapDispatchToProps(dispatch),
|
||||
|
||||
/**
|
||||
* Dispatches an action to set the lock i.e. password protection of the
|
||||
* conference/room.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object} - Dispatched action.
|
||||
* @type {Function}
|
||||
*/
|
||||
_onRoomLock() {
|
||||
return dispatch(beginRoomLockRequest());
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches between the front/user-facing and rear/environment-facing
|
||||
* cameras.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object} - Dispatched action.
|
||||
* @type {Function}
|
||||
*/
|
||||
_onToggleCameraFacingMode() {
|
||||
return dispatch(toggleCameraFacingMode());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of Redux store to React component props.
|
||||
*
|
||||
* @param {Object} state - Redux store.
|
||||
* @returns {{
|
||||
* _locked: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const conference = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is
|
||||
* locked/password-protected.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_locked: conference.locked
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbar);
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { resetAlwaysVisibleToolbar } from '../actions';
|
||||
import {
|
||||
abstractMapStateToProps,
|
||||
showCustomToolbarPopup
|
||||
} from '../functions';
|
||||
import Notice from './Notice';
|
||||
import PrimaryToolbar from './PrimaryToolbar';
|
||||
import SecondaryToolbar from './SecondaryToolbar';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Implements the conference toolbar on React.
|
||||
*/
|
||||
class Toolbar extends Component {
|
||||
|
||||
/**
|
||||
* App component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Handler dispatching reset always visible toolbar action.
|
||||
*/
|
||||
_onResetAlwaysVisibleToolbar: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Represents conference subject.
|
||||
*/
|
||||
_subject: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Flag showing whether to set subject slide in animation.
|
||||
*/
|
||||
_subjectSlideIn: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Property containing toolbar timeout id.
|
||||
*/
|
||||
_timeoutId: React.PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes reset always visible toolbar after mounting the component and
|
||||
* registers legacy UI listeners.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount(): void {
|
||||
this.props._onResetAlwaysVisibleToolbar();
|
||||
|
||||
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
|
||||
showCustomToolbarPopup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters legacy UI listeners.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount(): void {
|
||||
APP.UI.removeListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
|
||||
showCustomToolbarPopup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render(): ReactElement<*> {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this._renderSubject()
|
||||
}
|
||||
{
|
||||
this._renderToolbars()
|
||||
}
|
||||
<div id = 'sideToolbarContainer' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns React element representing toolbar subject.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
* @private
|
||||
*/
|
||||
_renderSubject(): ReactElement<*> | null {
|
||||
const { _subjectSlideIn, _subject } = this.props;
|
||||
const classNames = [ 'subject' ];
|
||||
|
||||
if (!_subject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_subjectSlideIn) {
|
||||
classNames.push('subject_slide-in');
|
||||
} else {
|
||||
classNames.push('subject_slide-out');
|
||||
}
|
||||
|
||||
// XXX: Since chat is now not reactified we have to dangerously set
|
||||
// inner HTML into the component. This has to be refactored while
|
||||
// reactification of the Chat.js
|
||||
const innerHtml = {
|
||||
__html: _subject
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { classNames.join(' ') }
|
||||
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML = { innerHtml }
|
||||
id = 'subject' />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders primary and secondary toolbars.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
* @private
|
||||
*/
|
||||
_renderToolbars(): ReactElement<*> | null {
|
||||
// We should not show the toolbars till timeout object will be
|
||||
// initialized.
|
||||
if (this.props._timeoutId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Notice />
|
||||
<PrimaryToolbar />
|
||||
<SecondaryToolbar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps parts of Redux actions to component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {{
|
||||
* _onResetAlwaysVisibleToolbar: Function
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Function): Object {
|
||||
return {
|
||||
|
||||
/**
|
||||
* Dispatches an action resetting always visible toolbar.
|
||||
*
|
||||
* @returns {Object} Dispatched action.
|
||||
*/
|
||||
_onResetAlwaysVisibleToolbar() {
|
||||
dispatch(resetAlwaysVisibleToolbar());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps parts of toolbar state to component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioMuted: boolean,
|
||||
* _locked: boolean,
|
||||
* _subjectSlideIn: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const {
|
||||
subject,
|
||||
subjectSlideIn,
|
||||
timeoutId
|
||||
} = state['features/toolbar'];
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
||||
/**
|
||||
* Property containing conference subject.
|
||||
*
|
||||
* @protected
|
||||
* @type {string}
|
||||
*/
|
||||
_subject: subject,
|
||||
|
||||
/**
|
||||
* Flag showing whether to set subject slide in animation.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_subjectSlideIn: subjectSlideIn,
|
||||
|
||||
/**
|
||||
* Property containing toolbar timeout id.
|
||||
*
|
||||
* @protected
|
||||
* @type {number}
|
||||
*/
|
||||
_timeoutId: timeoutId
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbar);
|
|
@ -0,0 +1,228 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import UIUtil from '../../../../modules/UI/util/UIUtil';
|
||||
|
||||
import AbstractToolbarButton from './AbstractToolbarButton';
|
||||
import { getButtonAttributesByProps } from '../functions';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Represents a button in Toolbar on React.
|
||||
*
|
||||
* @class ToolbarButton
|
||||
* @extends AbstractToolbarButton
|
||||
*/
|
||||
class ToolbarButton extends AbstractToolbarButton {
|
||||
_createRefToButton: Function;
|
||||
|
||||
_onClick: Function;
|
||||
|
||||
/**
|
||||
* Toolbar button component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractToolbarButton.propTypes,
|
||||
|
||||
/**
|
||||
* Object describing button.
|
||||
*/
|
||||
button: React.PropTypes.object.isRequired,
|
||||
|
||||
/**
|
||||
* Handler for component mount.
|
||||
*/
|
||||
onMount: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Handler for component unmount.
|
||||
*/
|
||||
onUnmount: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Translation helper function.
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes new ToolbarButton instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind methods to save the context
|
||||
this._createRefToButton = this._createRefToButton.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shortcut/tooltip
|
||||
* after mounting of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount(): void {
|
||||
this._setShortcutAndTooltip();
|
||||
|
||||
if (this.props.onMount) {
|
||||
this.props.onMount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes on unmount handler if it was passed to the props.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount(): void {
|
||||
if (this.props.onUnmount) {
|
||||
this.props.onUnmount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render(): ReactElement<*> {
|
||||
const { button } = this.props;
|
||||
const attributes = getButtonAttributesByProps(button);
|
||||
const popups = button.popups || [];
|
||||
|
||||
return (
|
||||
<a
|
||||
{ ...attributes }
|
||||
onClick = { this._onClick }
|
||||
ref = { this._createRefToButton }>
|
||||
{ this._renderInnerElementsIfRequired() }
|
||||
{ this._renderPopups(popups) }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates reference to current toolbar button.
|
||||
*
|
||||
* @param {HTMLElement} element - HTMLElement representing the toolbar
|
||||
* button.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_createRefToButton(element: HTMLElement): void {
|
||||
this.button = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper on on click handler props for current button.
|
||||
*
|
||||
* @param {Event} event - Click event object.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_onClick(event: Event): void {
|
||||
const {
|
||||
button,
|
||||
onClick
|
||||
} = this.props;
|
||||
const {
|
||||
enabled,
|
||||
unclickable
|
||||
} = button;
|
||||
|
||||
if (enabled && !unclickable && onClick) {
|
||||
onClick(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If toolbar button should contain children elements
|
||||
* renders them.
|
||||
*
|
||||
* @returns {ReactElement|null}
|
||||
* @private
|
||||
*/
|
||||
_renderInnerElementsIfRequired(): ReactElement<*> | null {
|
||||
if (this.props.button.html) {
|
||||
return this.props.button.html;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders popup element for toolbar button.
|
||||
*
|
||||
* @param {Array} popups - Array of popup objects.
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_renderPopups(popups: Array<*> = []): Array<*> {
|
||||
return popups.map(popup => {
|
||||
let gravity = 'n';
|
||||
|
||||
if (popup.dataAttrPosition) {
|
||||
gravity = popup.dataAttrPosition;
|
||||
}
|
||||
|
||||
const title = this.props.t(popup.dataAttr);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { popup.className }
|
||||
data-popup = { gravity }
|
||||
id = { popup.id }
|
||||
key = { popup.id }
|
||||
title = { title } />
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shortcut and tooltip for current toolbar button.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setShortcutAndTooltip(): void {
|
||||
const { button } = this.props;
|
||||
const name = button.buttonName;
|
||||
|
||||
if (UIUtil.isButtonEnabled(name)) {
|
||||
const tooltipPosition
|
||||
= interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) > -1
|
||||
? 'bottom' : 'right';
|
||||
|
||||
if (!button.unclickable) {
|
||||
UIUtil.setTooltip(this.button,
|
||||
button.tooltipKey,
|
||||
tooltipPosition);
|
||||
}
|
||||
|
||||
if (button.shortcut) {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
button.shortcut,
|
||||
button.shortcutAttr,
|
||||
button.shortcutFunc,
|
||||
button.shortcutDescription
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ToolbarButton);
|
|
@ -1,2 +1 @@
|
|||
export { default as Notice } from './Notice';
|
||||
export { default as Toolbar } from './Toolbar';
|
||||
|
|
|
@ -0,0 +1,389 @@
|
|||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
declare var JitsiMeetJS: Object;
|
||||
|
||||
/**
|
||||
* Shows SIP number dialog.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _showSIPNumberInput() {
|
||||
const defaultNumber = config.defaultSipNumber || '';
|
||||
const msgString
|
||||
= `<input class="input-control" name="sipNumber" type="text" value="${
|
||||
defaultNumber}" autofocus>`;
|
||||
|
||||
APP.UI.messageHandler.openTwoButtonDialog({
|
||||
focus: ':input:first',
|
||||
leftButtonKey: 'dialog.Dial',
|
||||
msgString,
|
||||
titleKey: 'dialog.sipMsg',
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
submitFunction(event, value, message, formValues) {
|
||||
const { sipNumber } = formValues;
|
||||
|
||||
if (value && sipNumber) {
|
||||
APP.UI.emitEvent(UIEvents.SIP_DIAL, sipNumber);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* All toolbar buttons' descriptions.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Object representing camera button.
|
||||
*/
|
||||
camera: {
|
||||
classNames: [ 'button', 'icon-camera' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_camera',
|
||||
onClick() {
|
||||
if (APP.conference.videoMuted) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_MUTED, false);
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_MUTED, true);
|
||||
}
|
||||
},
|
||||
shortcut: 'V',
|
||||
shortcutAttr: 'toggleVideoPopover',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
|
||||
APP.conference.toggleVideoMuted();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.videoMute',
|
||||
tooltipKey: 'toolbar.videomute'
|
||||
},
|
||||
|
||||
/**
|
||||
* The Object representing chat button.
|
||||
*/
|
||||
chat: {
|
||||
classNames: [ 'button', 'icon-chat' ],
|
||||
enabled: true,
|
||||
html: <span className = 'badge-round'>
|
||||
<span id = 'unreadMessages' />
|
||||
</span>,
|
||||
id: 'toolbar_button_chat',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
|
||||
},
|
||||
shortcut: 'C',
|
||||
shortcutAttr: 'toggleChatPopover',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleChat',
|
||||
sideContainerId: 'chat_container',
|
||||
tooltipKey: 'toolbar.chat'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing contact list button.
|
||||
*/
|
||||
contacts: {
|
||||
classNames: [ 'button', 'icon-contactList' ],
|
||||
enabled: true,
|
||||
|
||||
// XXX: Hotfix to solve race condition between toolbar rendering and
|
||||
// contact list view that updates the number of active participants
|
||||
// via jQuery. There is case when contact list view updates number of
|
||||
// participants but toolbar has not been rendered yet. Since this issue
|
||||
// is reproducible only for conferences with the only participant let's
|
||||
// use 1 participant as a default value for this badge. Later after
|
||||
// reactification of contact list let's use the value of active
|
||||
// paricipants from Redux store.
|
||||
html: <span className = 'badge-round'>
|
||||
<span id = 'numberOfParticipants'>1</span>
|
||||
</span>,
|
||||
id: 'toolbar_contact_list',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'toolbar.contacts.toggled');
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
|
||||
},
|
||||
sideContainerId: 'contacts_container',
|
||||
tooltipKey: 'bottomtoolbar.contactlist'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing desktop sharing button.
|
||||
*/
|
||||
desktop: {
|
||||
classNames: [ 'button', 'icon-share-desktop' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_desktopsharing',
|
||||
onClick() {
|
||||
if (APP.conference.isSharingScreen) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
|
||||
}
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||
},
|
||||
shortcut: 'D',
|
||||
shortcutAttr: 'toggleDesktopSharingPopover',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
|
||||
APP.conference.toggleScreenSharing();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
|
||||
tooltipKey: 'toolbar.sharescreen'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing dialpad button.
|
||||
*/
|
||||
dialpad: {
|
||||
classNames: [ 'button', 'icon-dialpad' ],
|
||||
enabled: true,
|
||||
|
||||
// TODO: remove it after UI.updateDTMFSupport fix
|
||||
hidden: true,
|
||||
id: 'toolbar_button_dialpad',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
|
||||
},
|
||||
tooltipKey: 'toolbar.dialpad'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing etherpad button.
|
||||
*/
|
||||
etherpad: {
|
||||
classNames: [ 'button', 'icon-share-doc' ],
|
||||
enabled: true,
|
||||
hidden: true,
|
||||
id: 'toolbar_button_etherpad',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
|
||||
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||
},
|
||||
tooltipKey: 'toolbar.etherpad'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing button toggling full screen mode.
|
||||
*/
|
||||
fullscreen: {
|
||||
classNames: [ 'button', 'icon-full-screen' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_fullScreen',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
|
||||
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
|
||||
},
|
||||
shortcut: 'S',
|
||||
shortcutAttr: 'toggleFullscreenPopover',
|
||||
shortcutDescription: 'keyboardShortcuts.fullScreen',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
|
||||
APP.UI.toggleFullScreen();
|
||||
},
|
||||
tooltipKey: 'toolbar.fullscreen'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing hanging the call up button.
|
||||
*/
|
||||
hangup: {
|
||||
classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_hangup',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
|
||||
APP.UI.emitEvent(UIEvents.HANGUP);
|
||||
},
|
||||
tooltipKey: 'toolbar.hangup'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing button showing invite user dialog.
|
||||
*/
|
||||
invite: {
|
||||
classNames: [ 'button', 'icon-link' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_link',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
|
||||
APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
|
||||
},
|
||||
tooltipKey: 'toolbar.invite'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing microphone button.
|
||||
*/
|
||||
microphone: {
|
||||
classNames: [ 'button', 'icon-microphone' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_mute',
|
||||
onClick() {
|
||||
const sharedVideoManager = APP.UI.getSharedVideoManager();
|
||||
|
||||
if (APP.conference.audioMuted) {
|
||||
// If there's a shared video with the volume "on" and we aren't
|
||||
// the video owner, we warn the user
|
||||
// that currently it's not possible to unmute.
|
||||
if (sharedVideoManager
|
||||
&& sharedVideoManager.isSharedVideoVolumeOn()
|
||||
&& !sharedVideoManager.isSharedVideoOwner()) {
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'#unableToUnmutePopup', true, 5000);
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
|
||||
}
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
|
||||
}
|
||||
},
|
||||
popups: [
|
||||
{
|
||||
className: 'loginmenu',
|
||||
dataAttr: 'toolbar.micMutedPopup',
|
||||
id: 'micMutedPopup'
|
||||
},
|
||||
{
|
||||
className: 'loginmenu',
|
||||
dataAttr: 'toolbar.unableToUnmutePopup',
|
||||
id: 'unableToUnmutePopup'
|
||||
},
|
||||
{
|
||||
className: 'loginmenu',
|
||||
dataAttr: 'toolbar.talkWhileMutedPopup',
|
||||
id: 'talkWhileMutedPopup'
|
||||
}
|
||||
],
|
||||
shortcut: 'M',
|
||||
shortcutAttr: 'mutePopover',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
|
||||
APP.conference.toggleAudioMuted();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.mute',
|
||||
tooltipKey: 'toolbar.mute'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing profile button.
|
||||
*/
|
||||
profile: {
|
||||
classNames: [ 'button' ],
|
||||
enabled: true,
|
||||
html: <img
|
||||
id = 'avatar'
|
||||
src = 'images/avatar2.png' />,
|
||||
id: 'toolbar_button_profile',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.profile.toggled');
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_PROFILE);
|
||||
},
|
||||
sideContainerId: 'profile_container',
|
||||
tooltipKey: 'profile.setDisplayNameLabel'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing "Raise hand" button.
|
||||
*/
|
||||
raisehand: {
|
||||
classNames: [ 'button', 'icon-raised-hand' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_raisehand',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
shortcut: 'R',
|
||||
shortcutAttr: 'raiseHandPopover',
|
||||
shortcutDescription: 'keyboardShortcuts.raiseHand',
|
||||
shortcutFunc() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.raisehand.clicked');
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
tooltipKey: 'toolbar.raiseHand'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing recording button. Requires additional
|
||||
* initialization in Recording module.
|
||||
*/
|
||||
recording: {
|
||||
classNames: [ 'button' ],
|
||||
enabled: true,
|
||||
|
||||
// will be displayed once the recording functionality is detected
|
||||
hidden: true,
|
||||
id: 'toolbar_button_record',
|
||||
tooltipKey: 'liveStreaming.buttonTooltip'
|
||||
},
|
||||
|
||||
/**
|
||||
* The objecr representing settings button.
|
||||
*/
|
||||
settings: {
|
||||
classNames: [ 'button', 'icon-settings' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_settings',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
|
||||
},
|
||||
sideContainerId: 'settings_container',
|
||||
tooltipKey: 'toolbar.Settings'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing sharing Youtube video button.
|
||||
*/
|
||||
sharedvideo: {
|
||||
classNames: [ 'button', 'icon-shared-video' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_sharedvideo',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
|
||||
APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
|
||||
},
|
||||
popups: [
|
||||
{
|
||||
className: 'loginmenu extendedToolbarPopup',
|
||||
dataAttr: 'toolbar.sharedVideoMutedPopup',
|
||||
dataAttrPosition: 'w',
|
||||
id: 'sharedVideoMutedPopup'
|
||||
}
|
||||
],
|
||||
tooltipKey: 'toolbar.sharedvideo'
|
||||
},
|
||||
|
||||
/**
|
||||
* The object representing SIP call.
|
||||
*/
|
||||
sip: {
|
||||
classNames: [ 'button', 'icon-telephone' ],
|
||||
enabled: true,
|
||||
|
||||
// Will be displayed once the SIP calls functionality is detected.
|
||||
hidden: true,
|
||||
id: 'toolbar_button_sip',
|
||||
onClick() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
|
||||
_showSIPNumberInput();
|
||||
},
|
||||
tooltipKey: 'toolbar.sip'
|
||||
}
|
||||
};
|
|
@ -0,0 +1,254 @@
|
|||
/* @flow */
|
||||
|
||||
import SideContainerToggler
|
||||
from '../../../modules/UI/side_pannels/SideContainerToggler';
|
||||
|
||||
import { appNavigate } from '../app';
|
||||
import { toggleAudioMuted, toggleVideoMuted } from '../base/media';
|
||||
|
||||
import defaultToolbarButtons from './defaultToolbarButtons';
|
||||
|
||||
declare var $: Function;
|
||||
declare var AJS: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
import type { Dispatch } from 'redux-thunk';
|
||||
|
||||
type MapOfAttributes = { [key: string]: * };
|
||||
|
||||
/**
|
||||
* Maps actions to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @returns {{
|
||||
* _onHangup: Function,
|
||||
* _onToggleAudio: Function,
|
||||
* _onToggleVideo: Function
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
export function abstractMapDispatchToProps(dispatch: Dispatch<*>): Object {
|
||||
return {
|
||||
/**
|
||||
* Dispatches action to leave the current conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onHangup() {
|
||||
// XXX We don't know here which value is effectively/internally
|
||||
// used when there's no valid room name to join. It isn't our
|
||||
// business to know that anyway. The undefined value is our
|
||||
// expression of (1) the lack of knowledge & (2) the desire to no
|
||||
// longer have a valid room name to join.
|
||||
return dispatch(appNavigate(undefined));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the
|
||||
* audio/microphone.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object} - Dispatched action.
|
||||
* @type {Function}
|
||||
*/
|
||||
_onToggleAudio() {
|
||||
return dispatch(toggleAudioMuted());
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the video/camera.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object} - Dispatched action.
|
||||
* @type {Function}
|
||||
*/
|
||||
_onToggleVideo() {
|
||||
return dispatch(toggleVideoMuted());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps parts of media state to component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* _audioMuted: boolean,
|
||||
* _videoMuted: boolean,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: Object): Object {
|
||||
const media = state['features/base/media'];
|
||||
const { visible } = state['features/toolbar'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* Flag showing that audio is muted.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_audioMuted: media.audio.muted,
|
||||
|
||||
/**
|
||||
* Flag showing whether video is muted.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_videoMuted: media.video.muted,
|
||||
|
||||
/**
|
||||
* Flag showing whether toolbar is visible.
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes toolbar button props and maps them to HTML attributes to set.
|
||||
*
|
||||
* @param {Object} props - Props set to the React component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getButtonAttributesByProps(props: Object): MapOfAttributes {
|
||||
const classNames = [ ...props.classNames ];
|
||||
|
||||
props.toggled && classNames.push('toggled');
|
||||
props.unclickable && classNames.push('unclickable');
|
||||
|
||||
const result: MapOfAttributes = {
|
||||
className: classNames.join(' '),
|
||||
'data-container': 'body',
|
||||
'data-placement': 'bottom',
|
||||
id: props.id
|
||||
};
|
||||
|
||||
if (!props.enabled) {
|
||||
result.disabled = 'disabled';
|
||||
}
|
||||
|
||||
if (props.hidden) {
|
||||
result.style = { display: 'none' };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object containing default buttons for the primary and secondary
|
||||
* toolbars.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getDefaultToolbarButtons(): Object {
|
||||
let toolbarButtons = {
|
||||
primaryToolbarButtons: new Map(),
|
||||
secondaryToolbarButtons: new Map()
|
||||
};
|
||||
|
||||
if (typeof interfaceConfig !== 'undefined'
|
||||
&& interfaceConfig.TOOLBAR_BUTTONS) {
|
||||
toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS.reduce(
|
||||
(acc, buttonName) => {
|
||||
const button = defaultToolbarButtons[buttonName];
|
||||
|
||||
if (button) {
|
||||
const place = _getToolbarButtonPlace(buttonName);
|
||||
|
||||
button.buttonName = buttonName;
|
||||
acc[place].set(buttonName, button);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, toolbarButtons);
|
||||
}
|
||||
|
||||
return toolbarButtons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get place for toolbar button.
|
||||
* Now it can be in main toolbar or in extended (left) toolbar.
|
||||
*
|
||||
* @param {string} btn - Button name.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getToolbarButtonPlace(btn) {
|
||||
return (
|
||||
interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn)
|
||||
? 'primaryToolbarButtons'
|
||||
: 'secondaryToolbarButtons');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns toolbar class names to add while rendering.
|
||||
*
|
||||
* @param {Object} props - Props object pass to React component.
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
export function getToolbarClassNames(props: Object) {
|
||||
const primaryToolbarClassNames = [ 'toolbar_primary' ];
|
||||
const secondaryToolbarClassNames = [ 'toolbar_secondary' ];
|
||||
|
||||
if (props._visible) {
|
||||
const slideInAnimation
|
||||
= SideContainerToggler.isVisible ? 'slideInExtX' : 'slideInX';
|
||||
|
||||
primaryToolbarClassNames.push('fadeIn');
|
||||
secondaryToolbarClassNames.push(slideInAnimation);
|
||||
} else {
|
||||
const slideOutAnimation
|
||||
= SideContainerToggler.isVisible ? 'slideOutExtX' : 'slideOutX';
|
||||
|
||||
primaryToolbarClassNames.push('fadeOut');
|
||||
secondaryToolbarClassNames.push(slideOutAnimation);
|
||||
}
|
||||
|
||||
return {
|
||||
primaryToolbarClassName: primaryToolbarClassNames.join(' '),
|
||||
secondaryToolbarClassName: secondaryToolbarClassNames.join(' ')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show custom popup/tooltip for a specified button.
|
||||
*
|
||||
* @param {string} popupSelectorID - The selector id of the popup to show.
|
||||
* @param {boolean} show - True or false/show or hide the popup.
|
||||
* @param {number} timeout - The time to show the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function showCustomToolbarPopup(
|
||||
popupSelectorID: string,
|
||||
show: boolean,
|
||||
timeout: number) {
|
||||
AJS.$(popupSelectorID).tooltip({
|
||||
gravity: $(popupSelectorID).attr('data-popup'),
|
||||
html: true,
|
||||
title: 'title',
|
||||
trigger: 'manual'
|
||||
});
|
||||
|
||||
if (show) {
|
||||
AJS.$(popupSelectorID).tooltip('show');
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
// hide the tooltip
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
},
|
||||
timeout);
|
||||
} else {
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
}
|
||||
}
|
|
@ -1 +1,6 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/* @flow */
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBAR_TIMEOUT,
|
||||
SET_TOOLBAR_TIMEOUT
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware that captures toolbar actions and handle changes in toolbar
|
||||
* timeout.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CLEAR_TOOLBAR_TIMEOUT: {
|
||||
const { timeoutId } = store.getState()['features/toolbar'];
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_TOOLBAR_TIMEOUT: {
|
||||
const { timeoutId } = store.getState()['features/toolbar'];
|
||||
const { handler, toolbarTimeout } = action;
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
const newTimeoutId = setTimeout(handler, toolbarTimeout);
|
||||
|
||||
action.timeoutId = newTimeoutId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
|
@ -0,0 +1,191 @@
|
|||
/* @flow */
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBAR_TIMEOUT,
|
||||
SET_ALWAYS_VISIBLE_TOOLBAR,
|
||||
SET_SUBJECT,
|
||||
SET_SUBJECT_SLIDE_IN,
|
||||
SET_TOOLBAR_BUTTON,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBAR_TIMEOUT,
|
||||
SET_TOOLBAR_TIMEOUT_NUMBER,
|
||||
SET_TOOLBAR_VISIBLE
|
||||
} from './actionTypes';
|
||||
import { getDefaultToolbarButtons } from './functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Returns initial state for toolbar's part of Redux store.
|
||||
*
|
||||
* @returns {{
|
||||
* primaryToolbarButtons: Map,
|
||||
* secondaryToolbarButtons: Map
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _getInitialState() {
|
||||
// Default toolbar timeout for mobile app.
|
||||
let toolbarTimeout = 5000;
|
||||
|
||||
if (typeof interfaceConfig !== 'undefined'
|
||||
&& interfaceConfig.INITIAL_TOOLBAR_TIMEOUT) {
|
||||
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Contains default toolbar buttons for primary and secondary toolbars.
|
||||
*
|
||||
* @type {Map}
|
||||
*/
|
||||
...getDefaultToolbarButtons(),
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is always visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
alwaysVisible: false,
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is hovered.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
hovered: false,
|
||||
|
||||
/**
|
||||
* Contains text of conference subject.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
subject: '',
|
||||
|
||||
/**
|
||||
* Shows whether subject is sliding in.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
subjectSlideIn: false,
|
||||
|
||||
/**
|
||||
* Contains toolbar timeout id.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
timeoutId: null,
|
||||
|
||||
/**
|
||||
* Contains delay of toolbar timeout.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
toolbarTimeout,
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
visible: false
|
||||
};
|
||||
}
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/toolbar',
|
||||
(state: Object = _getInitialState(), action: Object) => {
|
||||
switch (action.type) {
|
||||
case CLEAR_TOOLBAR_TIMEOUT:
|
||||
return {
|
||||
...state,
|
||||
timeoutId: undefined
|
||||
};
|
||||
|
||||
case SET_ALWAYS_VISIBLE_TOOLBAR:
|
||||
return {
|
||||
...state,
|
||||
alwaysVisible: action.alwaysVisible
|
||||
};
|
||||
|
||||
case SET_SUBJECT:
|
||||
return {
|
||||
...state,
|
||||
subject: action.subject
|
||||
};
|
||||
|
||||
case SET_SUBJECT_SLIDE_IN:
|
||||
return {
|
||||
...state,
|
||||
subjectSlideIn: action.subjectSlideIn
|
||||
};
|
||||
|
||||
case SET_TOOLBAR_BUTTON:
|
||||
return _setButton(state, action);
|
||||
|
||||
case SET_TOOLBAR_HOVERED:
|
||||
return {
|
||||
...state,
|
||||
hovered: action.hovered
|
||||
};
|
||||
|
||||
case SET_TOOLBAR_TIMEOUT:
|
||||
return {
|
||||
...state,
|
||||
toolbarTimeout: action.toolbarTimeout,
|
||||
timeoutId: action.timeoutId
|
||||
};
|
||||
|
||||
case SET_TOOLBAR_TIMEOUT_NUMBER:
|
||||
return {
|
||||
...state,
|
||||
toolbarTimeout: action.toolbarTimeout
|
||||
};
|
||||
|
||||
case SET_TOOLBAR_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
visible: action.visible
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets new value of the button.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {Object} action - Dispatched action.
|
||||
* @param {Object} action.button - Object describing toolbar button.
|
||||
* @param {Object} action.buttonName - The name of the button.
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
function _setButton(state, { buttonName, button }): Object {
|
||||
const {
|
||||
primaryToolbarButtons,
|
||||
secondaryToolbarButtons
|
||||
} = state;
|
||||
let selectedButton = primaryToolbarButtons.get(buttonName);
|
||||
let place = 'primaryToolbarButtons';
|
||||
|
||||
if (!selectedButton) {
|
||||
selectedButton = secondaryToolbarButtons.get(buttonName);
|
||||
place = 'secondaryToolbarButtons';
|
||||
}
|
||||
|
||||
selectedButton = {
|
||||
...selectedButton,
|
||||
...button
|
||||
};
|
||||
|
||||
const updatedToolbar = state[place].set(buttonName, selectedButton);
|
||||
|
||||
return {
|
||||
...state,
|
||||
[place]: new Map(updatedToolbar)
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue