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 EventEmitter from "events";
|
||||||
|
|
||||||
|
import { showDesktopSharingButton } from './react/features/toolbar';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AVATAR_ID_COMMAND,
|
AVATAR_ID_COMMAND,
|
||||||
AVATAR_URL_COMMAND,
|
AVATAR_URL_COMMAND,
|
||||||
|
@ -583,6 +585,9 @@ export default {
|
||||||
APP.connection = connection = con;
|
APP.connection = connection = con;
|
||||||
this.isDesktopSharingEnabled =
|
this.isDesktopSharingEnabled =
|
||||||
JitsiMeetJS.isDesktopSharingEnabled();
|
JitsiMeetJS.isDesktopSharingEnabled();
|
||||||
|
|
||||||
|
APP.store.dispatch(showDesktopSharingButton());
|
||||||
|
|
||||||
APP.remoteControl.init();
|
APP.remoteControl.init();
|
||||||
this._createRoom(tracks);
|
this._createRoom(tracks);
|
||||||
|
|
||||||
|
|
|
@ -66,18 +66,4 @@
|
||||||
@include keyframes(slideInExtContainer) {
|
@include keyframes(slideInExtContainer) {
|
||||||
from { width: 0; }
|
from { width: 0; }
|
||||||
to { width: $sidebarWidth; }
|
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 {
|
.notice {
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
z-index: $zindex3;
|
z-index: $zindex3;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
|
||||||
|
@include transform(translateX(-50%));
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
|
@ -1,184 +1,234 @@
|
||||||
.toolbar {
|
|
||||||
background-color: $toolbarBackground;
|
|
||||||
position: relative;
|
|
||||||
z-index: $toolbarZ;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splitter button in the toolbar.
|
|
||||||
*/
|
|
||||||
&__splitter {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 1px;
|
|
||||||
height: 50%;
|
|
||||||
margin: 0 $splitterToolbarButtonMargin;
|
|
||||||
background: $splitterColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#mainToolbarContainer{
|
|
||||||
display: block;
|
|
||||||
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;
|
|
||||||
top: 30px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: auto;
|
|
||||||
border-radius: 3px;
|
|
||||||
.button:first-child {
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
border-top-left-radius: 3px;
|
|
||||||
}
|
|
||||||
.button:last-child {
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#extendedToolbar {
|
|
||||||
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;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
transform: translateX(-100%);
|
|
||||||
-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 {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#feedbackButton {
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Round badge.
|
* Round badge.
|
||||||
*/
|
*/
|
||||||
.badge-round {
|
.badge-round {
|
||||||
background-color: $toolbarBadgeBackground;
|
background-color: $toolbarBadgeBackground;
|
||||||
color: $toolbarBadgeColor;
|
|
||||||
font-size: 9px;
|
|
||||||
line-height: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
min-width: 13px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
vertical-align: middle;
|
color: $toolbarBadgeColor;
|
||||||
// Do not inherit the font-family from the toolbar button, because it's an
|
// Do not inherit the font-family from the toolbar button, because it's an
|
||||||
// icon style.
|
// icon style.
|
||||||
font-family: $baseFontFamily;
|
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 specific round badge.
|
* Toolbar button styles.
|
||||||
*/
|
*/
|
||||||
.toolbar .badge-round {
|
.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;
|
position: absolute;
|
||||||
right: 9px;
|
right:0;
|
||||||
bottom: 9px;
|
text-align: center;
|
||||||
|
top:0;
|
||||||
|
z-index: $toolbarZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common toolbar styles.
|
||||||
|
*/
|
||||||
|
.toolbar {
|
||||||
|
background-color: $toolbarBackground;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: $toolbarZ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splitter button in the toolbar.
|
||||||
|
*/
|
||||||
|
&__splitter {
|
||||||
|
background: $splitterColor;
|
||||||
|
display: inline-block;
|
||||||
|
height: 50%;
|
||||||
|
margin: 0 $splitterToolbarButtonMargin;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary toolbar styles.
|
||||||
|
*/
|
||||||
|
&_primary {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 30px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.button:last-child {
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_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;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
left: 0;
|
||||||
|
padding-top: 10px;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
width: $defaultToolbarSize;
|
||||||
|
-webkit-transform: translateX(-100%);
|
||||||
|
|
||||||
|
.button.toggled:not(.icon-raised-hand) {
|
||||||
|
background: $toolbarSelectBackground;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.unclickable {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover, &:active, &.selected {
|
||||||
|
background: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar specific round badge.
|
||||||
|
*/
|
||||||
|
.badge-round {
|
||||||
|
bottom: 9px;
|
||||||
|
position: absolute;
|
||||||
|
right: 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
|
* START of fade in animation for main toolbar
|
||||||
*/
|
*/
|
||||||
.fadeIn {
|
.fadeIn {
|
||||||
@include animation('fadeIn .3s linear .2s forwards');
|
opacity: 1;
|
||||||
|
|
||||||
|
@include transition(all .3s ease-in);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeOut {
|
.fadeOut {
|
||||||
@include animation('fadeOut .5s linear forwards');
|
opacity: 0;
|
||||||
|
|
||||||
|
@include transition(all .3s ease-out);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
* Style variables
|
* Style variables
|
||||||
*/
|
*/
|
||||||
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
$toolbarFontSize: 1.9em;
|
$hangupColor: #bf2117;
|
||||||
$hangupFontSize: 2em;
|
$hangupFontSize: 2em;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size variables.
|
* Size variables.
|
||||||
*/
|
*/
|
||||||
$defaultToolbarSize: 50px;
|
|
||||||
|
|
||||||
// Video layout.
|
// Video layout.
|
||||||
$thumbnailToolbarHeight: 22px;
|
$thumbnailToolbarHeight: 22px;
|
||||||
|
@ -34,14 +33,16 @@ $tooltipBg: rgba(0,0,0, 0.7);
|
||||||
/**
|
/**
|
||||||
* Toolbar
|
* Toolbar
|
||||||
*/
|
*/
|
||||||
$toolbarTitleColor: #FFFFFF;
|
$defaultToolbarSize: 50px;
|
||||||
$toolbarTitleFontSize: 19px;
|
$splitterToolbarButtonMargin: 18px;
|
||||||
$toolbarBackground: rgba(0, 0, 0, 0.5);
|
$toolbarBackground: rgba(0, 0, 0, 0.5);
|
||||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
|
||||||
$toolbarBadgeBackground: #165ECC;
|
$toolbarBadgeBackground: #165ECC;
|
||||||
$toolbarBadgeColor: #FFFFFF;
|
$toolbarBadgeColor: #FFFFFF;
|
||||||
|
$toolbarFontSize: 1.9em;
|
||||||
|
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||||
|
$toolbarTitleColor: #FFFFFF;
|
||||||
|
$toolbarTitleFontSize: 19px;
|
||||||
$toolbarToggleBackground: #12499C;
|
$toolbarToggleBackground: #12499C;
|
||||||
$splitterToolbarButtonMargin: 18px;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main controls
|
* Main controls
|
||||||
|
|
|
@ -6,8 +6,6 @@ var UI = {};
|
||||||
|
|
||||||
import Chat from "./side_pannels/chat/Chat";
|
import Chat from "./side_pannels/chat/Chat";
|
||||||
import SidePanels from "./side_pannels/SidePanels";
|
import SidePanels from "./side_pannels/SidePanels";
|
||||||
import Toolbar from "./toolbars/Toolbar";
|
|
||||||
import ToolbarToggler from "./toolbars/ToolbarToggler";
|
|
||||||
import Avatar from "./avatar/Avatar";
|
import Avatar from "./avatar/Avatar";
|
||||||
import SideContainerToggler from "./side_pannels/SideContainerToggler";
|
import SideContainerToggler from "./side_pannels/SideContainerToggler";
|
||||||
import UIUtil from "./util/UIUtil";
|
import UIUtil from "./util/UIUtil";
|
||||||
|
@ -25,6 +23,23 @@ import RingOverlay from "./ring_overlay/RingOverlay";
|
||||||
import UIErrors from './UIErrors';
|
import UIErrors from './UIErrors';
|
||||||
import { debounce } from "../util/helpers";
|
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");
|
var EventEmitter = require("events");
|
||||||
UI.messageHandler = require("./util/MessageHandler");
|
UI.messageHandler = require("./util/MessageHandler");
|
||||||
var messageHandler = UI.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]
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
|
||||||
= "dialog.micNotSendingData";
|
= "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
|
* Toggles the application in and out of full screen mode
|
||||||
* (a.k.a. presentation mode in Chrome).
|
* (a.k.a. presentation mode in Chrome).
|
||||||
|
@ -231,7 +236,7 @@ UI.initConference = function () {
|
||||||
UI.setUserAvatarID(id, Settings.getAvatarId());
|
UI.setUserAvatarID(id, Settings.getAvatarId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Toolbar.checkAutoEnableDesktopSharing();
|
APP.store.dispatch(checkAutoEnableDesktopSharing());
|
||||||
|
|
||||||
if(!interfaceConfig.filmStripOnly) {
|
if(!interfaceConfig.filmStripOnly) {
|
||||||
Feedback.init(eventEmitter);
|
Feedback.init(eventEmitter);
|
||||||
|
@ -294,7 +299,6 @@ UI.start = function () {
|
||||||
// Set the defaults for tooltips.
|
// Set the defaults for tooltips.
|
||||||
_setTooltipDefaults();
|
_setTooltipDefaults();
|
||||||
|
|
||||||
ToolbarToggler.init();
|
|
||||||
SideContainerToggler.init(eventEmitter);
|
SideContainerToggler.init(eventEmitter);
|
||||||
FilmStrip.init(eventEmitter);
|
FilmStrip.init(eventEmitter);
|
||||||
|
|
||||||
|
@ -313,11 +317,9 @@ UI.start = function () {
|
||||||
{ leading: true, trailing: false });
|
{ leading: true, trailing: false });
|
||||||
|
|
||||||
$("#videoconference_page").mousemove(debouncedShowToolbar);
|
$("#videoconference_page").mousemove(debouncedShowToolbar);
|
||||||
setupToolbars();
|
|
||||||
|
|
||||||
// Initialise the recording module.
|
// Initialize side panels
|
||||||
if (config.enableRecording)
|
SidePanels.init(eventEmitter);
|
||||||
Recording.init(eventEmitter, config.recordingType);
|
|
||||||
} else {
|
} else {
|
||||||
$("body").addClass("filmstrip-only");
|
$("body").addClass("filmstrip-only");
|
||||||
UIUtil.setVisible('mainToolbarContainer', false);
|
UIUtil.setVisible('mainToolbarContainer', false);
|
||||||
|
@ -446,7 +448,8 @@ UI.initEtherpad = name => {
|
||||||
logger.log('Etherpad is enabled');
|
logger.log('Etherpad is enabled');
|
||||||
etherpadManager
|
etherpadManager
|
||||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||||
Toolbar.showEtherpadButton();
|
|
||||||
|
APP.store.dispatch(showEtherpadButton());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -521,8 +524,9 @@ UI.onPeerVideoTypeChanged
|
||||||
UI.updateLocalRole = isModerator => {
|
UI.updateLocalRole = isModerator => {
|
||||||
VideoLayout.showModeratorIndicator();
|
VideoLayout.showModeratorIndicator();
|
||||||
|
|
||||||
Toolbar.showSipCallButton(isModerator);
|
APP.store.dispatch(showSIPCallButton(isModerator));
|
||||||
Toolbar.showSharedVideoButton(isModerator);
|
APP.store.dispatch(showSharedVideoButton());
|
||||||
|
|
||||||
Recording.showRecordingButton(isModerator);
|
Recording.showRecordingButton(isModerator);
|
||||||
SettingsMenu.showStartMutedOptions(isModerator);
|
SettingsMenu.showStartMutedOptions(isModerator);
|
||||||
SettingsMenu.showFollowMeOptions(isModerator);
|
SettingsMenu.showFollowMeOptions(isModerator);
|
||||||
|
@ -676,7 +680,10 @@ UI.askForNickname = function () {
|
||||||
UI.setAudioMuted = function (id, muted) {
|
UI.setAudioMuted = function (id, muted) {
|
||||||
VideoLayout.onAudioMute(id, muted);
|
VideoLayout.onAudioMute(id, muted);
|
||||||
if (APP.conference.isLocalId(id)) {
|
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) {
|
UI.setVideoMuted = function (id, muted) {
|
||||||
VideoLayout.onVideoMute(id, muted);
|
VideoLayout.onVideoMute(id, muted);
|
||||||
if (APP.conference.isLocalId(id)) {
|
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 type the type of the event we're emitting
|
||||||
* @param options the parameters for the event
|
* @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) {
|
UI.clickOnVideo = function (videoNumber) {
|
||||||
let videos = $("#remoteVideos .videocontainer:not(#mixedstream)");
|
let videos = $("#remoteVideos .videocontainer:not(#mixedstream)");
|
||||||
|
@ -731,12 +741,12 @@ UI.clickOnVideo = function (videoNumber) {
|
||||||
|
|
||||||
//Used by torture
|
//Used by torture
|
||||||
UI.showToolbar = function (timeout) {
|
UI.showToolbar = function (timeout) {
|
||||||
return ToolbarToggler.showToolbar(timeout);
|
APP.store.dispatch(showToolbar(timeout));
|
||||||
};
|
};
|
||||||
|
|
||||||
//Used by torture
|
//Used by torture
|
||||||
UI.dockToolbar = function (isDock) {
|
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.
|
* Update state of desktop sharing buttons.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
UI.updateDesktopSharingButtons = function () {
|
UI.updateDesktopSharingButtons
|
||||||
Toolbar.updateDesktopSharingButtonState();
|
= () =>
|
||||||
};
|
APP.store.dispatch(setToolbarButton('desktop', {
|
||||||
|
toggled: APP.conference.isSharingScreen
|
||||||
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide connection quality statistics from UI.
|
* Hide connection quality statistics from UI.
|
||||||
|
@ -970,11 +984,8 @@ UI.addMessage = function (from, displayName, message, stamp) {
|
||||||
Chat.updateChatConversation(from, displayName, message, stamp);
|
Chat.updateChatConversation(from, displayName, message, stamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
UI.updateDTMFSupport
|
||||||
UI.updateDTMFSupport = function (isDTMFSupported) {
|
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
|
||||||
//TODO: enable when the UI is ready
|
|
||||||
//Toolbar.showDialPadButton(isDTMFSupported);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show user feedback dialog if its required and enabled after pressing the
|
* 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
|
* @param {boolean} enabled indicates if the camera button should be enabled
|
||||||
* or disabled
|
* or disabled
|
||||||
*/
|
*/
|
||||||
UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
|
UI.setCameraButtonEnabled
|
||||||
|
= enabled => APP.store.dispatch(setVideoIconEnabled(enabled));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables / disables microphone toolbar button.
|
* 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
|
* @param {boolean} enabled indicates if the microphone button should be
|
||||||
* enabled or disabled
|
* enabled or disabled
|
||||||
*/
|
*/
|
||||||
UI.setMicrophoneButtonEnabled = enabled => Toolbar.setAudioIconEnabled(enabled);
|
UI.setMicrophoneButtonEnabled
|
||||||
|
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||||
|
|
||||||
UI.showRingOverlay = function () {
|
UI.showRingOverlay = function () {
|
||||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||||
|
|
|
@ -20,7 +20,8 @@ import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from '../util/UIUtil';
|
import UIUtil from '../util/UIUtil';
|
||||||
import VideoLayout from '../videolayout/VideoLayout';
|
import VideoLayout from '../videolayout/VideoLayout';
|
||||||
import Feedback from '../feedback/Feedback.js';
|
import Feedback from '../feedback/Feedback.js';
|
||||||
import Toolbar from '../toolbars/Toolbar';
|
|
||||||
|
import { hideToolbar } from '../../../react/features/toolbar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dialog for user input.
|
* The dialog for user input.
|
||||||
|
@ -263,7 +264,7 @@ var Recording = {
|
||||||
APP.conference.getMyUserId(), false);
|
APP.conference.getMyUserId(), false);
|
||||||
VideoLayout.setLocalVideoVisible(false);
|
VideoLayout.setLocalVideoVisible(false);
|
||||||
Feedback.enableFeedback(false);
|
Feedback.enableFeedback(false);
|
||||||
Toolbar.enable(false);
|
APP.store.dispatch(hideToolbar());
|
||||||
APP.UI.messageHandler.enableNotifications(false);
|
APP.UI.messageHandler.enableNotifications(false);
|
||||||
APP.UI.messageHandler.enablePopups(false);
|
APP.UI.messageHandler.enablePopups(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ import VideoLayout from "../videolayout/VideoLayout";
|
||||||
import LargeContainer from '../videolayout/LargeContainer';
|
import LargeContainer from '../videolayout/LargeContainer';
|
||||||
import SmallVideo from '../videolayout/SmallVideo';
|
import SmallVideo from '../videolayout/SmallVideo';
|
||||||
import FilmStrip from '../videolayout/FilmStrip';
|
import FilmStrip from '../videolayout/FilmStrip';
|
||||||
import ToolbarToggler from "../toolbars/ToolbarToggler";
|
|
||||||
|
import { dockToolbar, showToolbar } from '../../../react/features/toolbar';
|
||||||
|
|
||||||
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
||||||
|
|
||||||
|
@ -578,7 +579,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||||
self.bodyBackground = document.body.style.background;
|
self.bodyBackground = document.body.style.background;
|
||||||
document.body.style.background = 'black';
|
document.body.style.background = 'black';
|
||||||
this.$iframe.css({opacity: 1});
|
this.$iframe.css({opacity: 1});
|
||||||
ToolbarToggler.dockToolbar(true);
|
APP.store.dispatch(dockToolbar(true));
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -586,7 +587,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||||
|
|
||||||
hide () {
|
hide () {
|
||||||
let self = this;
|
let self = this;
|
||||||
ToolbarToggler.dockToolbar(false);
|
APP.store.dispatch(dockToolbar(false));
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.$iframe.fadeOut(300, () => {
|
this.$iframe.fadeOut(300, () => {
|
||||||
document.body.style.background = self.bodyBackground;
|
document.body.style.background = self.bodyBackground;
|
||||||
|
@ -597,7 +598,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoverIn () {
|
onHoverIn () {
|
||||||
ToolbarToggler.showToolbar();
|
APP.store.dispatch(showToolbar());
|
||||||
}
|
}
|
||||||
|
|
||||||
get id () {
|
get id () {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import {processReplacements, linkify} from './Replacement';
|
import {processReplacements, linkify} from './Replacement';
|
||||||
import CommandsProcessor from './Commands';
|
import CommandsProcessor from './Commands';
|
||||||
import ToolbarToggler from '../../toolbars/ToolbarToggler';
|
|
||||||
import VideoLayout from "../../videolayout/VideoLayout";
|
import VideoLayout from "../../videolayout/VideoLayout";
|
||||||
|
|
||||||
import UIUtil from '../../util/UIUtil';
|
import UIUtil from '../../util/UIUtil';
|
||||||
|
@ -10,6 +9,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
import { smileys } from './smileys';
|
import { smileys } from './smileys';
|
||||||
|
|
||||||
|
import { dockToolbar, setSubject } from '../../../../react/features/toolbar';
|
||||||
|
|
||||||
let unreadMessages = 0;
|
let unreadMessages = 0;
|
||||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||||
const htmlStr = `
|
const htmlStr = `
|
||||||
|
@ -59,7 +60,7 @@ function updateVisualNotification() {
|
||||||
if (unreadMessages) {
|
if (unreadMessages) {
|
||||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||||
|
|
||||||
ToolbarToggler.dockToolbar(true);
|
APP.store.dispatch(dockToolbar(true));
|
||||||
|
|
||||||
const chatButtonElement
|
const chatButtonElement
|
||||||
= document.getElementById('toolbar_button_chat');
|
= 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
|
// Undock the toolbar when the chat is shown and if we're in a
|
||||||
// video mode.
|
// video mode.
|
||||||
if (VideoLayout.isLargeVideoVisible()) {
|
if (VideoLayout.isLargeVideoVisible()) {
|
||||||
ToolbarToggler.dockToolbar(false);
|
APP.store.dispatch(dockToolbar(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are in conversation mode focus on the text input
|
// if we are in conversation mode focus on the text input
|
||||||
|
@ -319,10 +320,9 @@ var Chat = {
|
||||||
subject = subject.trim();
|
subject = subject.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
let subjectId = 'subject';
|
|
||||||
const html = linkify(UIUtil.escapeHtml(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 { Container } from '../../base/react';
|
||||||
import { FilmStrip } from '../../film-strip';
|
import { FilmStrip } from '../../film-strip';
|
||||||
import { LargeVideo } from '../../large-video';
|
import { LargeVideo } from '../../large-video';
|
||||||
import { Toolbar } from '../../toolbar';
|
import { setToolbarVisible, Toolbar } from '../../toolbar';
|
||||||
|
|
||||||
import { styles } from './styles';
|
import { styles } from './styles';
|
||||||
|
|
||||||
|
@ -28,8 +28,39 @@ class Conference extends Component {
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
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.
|
* Initializes a new Conference instance.
|
||||||
|
@ -40,8 +71,6 @@ class Conference extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = { toolbarVisible: true };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The numerical ID of the timeout in milliseconds after which the
|
* The numerical ID of the timeout in milliseconds after which the
|
||||||
* toolbar will be hidden. To be used with
|
* toolbar will be hidden. To be used with
|
||||||
|
@ -62,7 +91,7 @@ class Conference extends Component {
|
||||||
* returns {void}
|
* returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._setToolbarTimeout(this.state.toolbarVisible);
|
this._setToolbarTimeout(this.props._toolbarVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +101,7 @@ class Conference extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.dispatch(connect());
|
this.props._onConnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,7 +114,7 @@ class Conference extends Component {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._clearToolbarTimeout();
|
this._clearToolbarTimeout();
|
||||||
|
|
||||||
this.props.dispatch(disconnect());
|
this.props._onDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,8 +124,6 @@ class Conference extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const toolbarVisible = this.state.toolbarVisible;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
onClick = { this._onClick }
|
onClick = { this._onClick }
|
||||||
|
@ -104,11 +131,11 @@ class Conference extends Component {
|
||||||
touchFeedback = { false }>
|
touchFeedback = { false }>
|
||||||
|
|
||||||
<LargeVideo />
|
<LargeVideo />
|
||||||
<Toolbar visible = { toolbarVisible } />
|
|
||||||
<FilmStrip visible = { !toolbarVisible } />
|
<Toolbar />
|
||||||
|
<FilmStrip />
|
||||||
|
|
||||||
<DialogContainer />
|
<DialogContainer />
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -135,10 +162,9 @@ class Conference extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onClick() {
|
_onClick() {
|
||||||
const toolbarVisible = !this.state.toolbarVisible;
|
const toolbarVisible = !this.props._toolbarVisible;
|
||||||
|
|
||||||
this.setState({ toolbarVisible });
|
|
||||||
|
|
||||||
|
this.props._setToolbarVisible(toolbarVisible);
|
||||||
this._setToolbarTimeout(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 { connect, disconnect } from '../../base/connection';
|
||||||
import { DialogContainer } from '../../base/dialog';
|
import { DialogContainer } from '../../base/dialog';
|
||||||
import { Watermarks } from '../../base/react';
|
import { Watermarks } from '../../base/react';
|
||||||
import { FeedbackButton } from '../../feedback';
|
|
||||||
import { OverlayContainer } from '../../overlay';
|
import { OverlayContainer } from '../../overlay';
|
||||||
import { Notice } from '../../toolbar';
|
import { Toolbar } from '../../toolbar';
|
||||||
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
||||||
|
|
||||||
declare var $: Function;
|
declare var $: Function;
|
||||||
|
@ -66,25 +65,8 @@ class Conference extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id = 'videoconference_page'>
|
<div id = 'videoconference_page'>
|
||||||
<div id = 'mainToolbarContainer'>
|
<Toolbar />
|
||||||
<Notice />
|
|
||||||
|
|
||||||
<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 id = 'videospace'>
|
||||||
<div
|
<div
|
||||||
className = 'videocontainer'
|
className = 'videocontainer'
|
||||||
|
@ -154,6 +136,7 @@ class Conference extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogContainer />
|
<DialogContainer />
|
||||||
<OverlayContainer />
|
<OverlayContainer />
|
||||||
<HideNotificationBarStyle />
|
<HideNotificationBarStyle />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { Container } from '../../base/react';
|
import { Container } from '../../base/react';
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class FilmStrip extends Component {
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
visible: React.PropTypes.bool.isRequired
|
_visible: React.PropTypes.bool.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,7 @@ class FilmStrip extends Component {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
style = { styles.filmStrip }
|
style = { styles.filmStrip }
|
||||||
visible = { this.props.visible }>
|
visible = { this.props._visible }>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|
||||||
// eslint-disable-next-line react/jsx-curly-spacing
|
// eslint-disable-next-line react/jsx-curly-spacing
|
||||||
|
@ -109,6 +109,7 @@ class FilmStrip extends Component {
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _participants: Participant[],
|
* _participants: Participant[],
|
||||||
|
* _visible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
|
@ -119,7 +120,19 @@ function _mapStateToProps(state) {
|
||||||
* @private
|
* @private
|
||||||
* @type {Participant[]}
|
* @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 { View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
|
import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
|
||||||
import { Container } from '../../base/react';
|
import { Container } from '../../base/react';
|
||||||
import { ColorPalette } from '../../base/styles';
|
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 { styles } from './styles';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the conference toolbar on React Native.
|
* Implements the conference toolbar on React Native.
|
||||||
*
|
|
||||||
* @extends AbstractToolbar
|
|
||||||
*/
|
*/
|
||||||
class Toolbar extends AbstractToolbar {
|
class Toolbar extends Component {
|
||||||
/**
|
/**
|
||||||
* Toolbar component's property types.
|
* Toolbar component's property types.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = AbstractToolbar.propTypes
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Flag showing that audio is muted.
|
||||||
|
*/
|
||||||
|
_audioMuted: React.PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new Toolbar instance.
|
* Flag showing whether room is locked.
|
||||||
*
|
*/
|
||||||
* @param {Object} props - The read-only React Component props with which
|
_locked: React.PropTypes.bool,
|
||||||
* the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once for every instance.
|
/**
|
||||||
this._toggleCameraFacingMode
|
* Handler for hangup.
|
||||||
= this._toggleCameraFacingMode.bind(this);
|
*/
|
||||||
}
|
_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()}.
|
* Implements React's {@link Component#render()}.
|
||||||
|
@ -47,7 +80,7 @@ class Toolbar extends AbstractToolbar {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
style = { styles.toolbarContainer }
|
style = { styles.toolbarContainer }
|
||||||
visible = { this.props.visible }>
|
visible = { this.props._visible }>
|
||||||
{
|
{
|
||||||
this._renderPrimaryToolbar()
|
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,
|
* Renders the toolbar which contains the primary buttons such as hangup,
|
||||||
* audio and video mute.
|
* audio and video mute.
|
||||||
|
@ -76,12 +146,12 @@ class Toolbar extends AbstractToolbar {
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
iconName = { audioButtonStyles.iconName }
|
iconName = { audioButtonStyles.iconName }
|
||||||
iconStyle = { audioButtonStyles.iconStyle }
|
iconStyle = { audioButtonStyles.iconStyle }
|
||||||
onClick = { this._toggleAudio }
|
onClick = { this.props._onToggleAudio }
|
||||||
style = { audioButtonStyles.style } />
|
style = { audioButtonStyles.style } />
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
iconName = 'hangup'
|
iconName = 'hangup'
|
||||||
iconStyle = { styles.whiteIcon }
|
iconStyle = { styles.whiteIcon }
|
||||||
onClick = { this._onHangup }
|
onClick = { this.props._onHangup }
|
||||||
style = {{
|
style = {{
|
||||||
...styles.primaryToolbarButton,
|
...styles.primaryToolbarButton,
|
||||||
backgroundColor: ColorPalette.red
|
backgroundColor: ColorPalette.red
|
||||||
|
@ -90,7 +160,7 @@ class Toolbar extends AbstractToolbar {
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
iconName = { videoButtonStyles.iconName }
|
iconName = { videoButtonStyles.iconName }
|
||||||
iconStyle = { videoButtonStyles.iconStyle }
|
iconStyle = { videoButtonStyles.iconStyle }
|
||||||
onClick = { this._toggleVideo }
|
onClick = { this.props._onToggleVideo }
|
||||||
style = { videoButtonStyles.style } />
|
style = { videoButtonStyles.style } />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -125,7 +195,7 @@ class Toolbar extends AbstractToolbar {
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
iconName = 'switch-camera'
|
iconName = 'switch-camera'
|
||||||
iconStyle = { iconStyle }
|
iconStyle = { iconStyle }
|
||||||
onClick = { this._toggleCameraFacingMode }
|
onClick = { this.props._onToggleCameraFacingMode }
|
||||||
style = { style }
|
style = { style }
|
||||||
underlayColor = { underlayColor } />
|
underlayColor = { underlayColor } />
|
||||||
*/}
|
*/}
|
||||||
|
@ -134,7 +204,7 @@ class Toolbar extends AbstractToolbar {
|
||||||
this.props._locked ? 'security-locked' : 'security'
|
this.props._locked ? 'security-locked' : 'security'
|
||||||
}
|
}
|
||||||
iconStyle = { iconStyle }
|
iconStyle = { iconStyle }
|
||||||
onClick = { this._onRoomLock }
|
onClick = { this.props._onRoomLock }
|
||||||
style = { style }
|
style = { style }
|
||||||
underlayColor = { underlayColor } />
|
underlayColor = { underlayColor } />
|
||||||
</View>
|
</View>
|
||||||
|
@ -142,17 +212,6 @@ class Toolbar extends AbstractToolbar {
|
||||||
|
|
||||||
/* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
|
/* 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'
|
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';
|
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';
|
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