Merge remote-tracking branch 'bigbluebutton/master' into merge-master-into-2x
# Conflicts: # bigbluebutton-client/resources/config.xml.template # bigbluebutton-client/src/org/bigbluebutton/modules/phone/PhoneOptions.as # bigbluebutton-client/src/org/bigbluebutton/modules/phone/views/components/ToolbarButton.mxml # record-and-playback/core/scripts/rap-process-worker.rb
This commit is contained in:
commit
7d283cd154
@ -93,8 +93,17 @@ if (request.getParameterMap().isEmpty()) {
|
||||
//
|
||||
|
||||
String username = request.getParameter("username");
|
||||
String meetingname = request.getParameter("meetingname");
|
||||
boolean isModerator = Boolean.parseBoolean(request.getParameter("isModerator"));
|
||||
|
||||
// set defaults and overwrite them if custom values exist
|
||||
String meetingname = "Demo Meeting";
|
||||
if (request.getParameter("meetingname") != null) {
|
||||
meetingname = request.getParameter("meetingname");
|
||||
}
|
||||
|
||||
boolean isModerator = false;
|
||||
if (request.getParameter("isModerator") != null) {
|
||||
isModerator = Boolean.parseBoolean(request.getParameter("isModerator"));
|
||||
}
|
||||
|
||||
String joinURL = getJoinURLHTML5(username, meetingname, "false", null, null, null, isModerator);
|
||||
Document doc = null;
|
||||
|
@ -465,7 +465,7 @@
|
||||
file="BigBlueButton.html"
|
||||
height="100%"
|
||||
width="100%"
|
||||
bgcolor="grey"
|
||||
bgcolor="white"
|
||||
application="BBB"
|
||||
swf="BigBlueButton"
|
||||
version-major="10"
|
||||
|
@ -55,8 +55,6 @@
|
||||
uri="rtmp://HOST/sip"
|
||||
autoJoin="true"
|
||||
listenOnlyMode="true"
|
||||
forceListenOnly="false"
|
||||
presenterShareOnly="false"
|
||||
skipCheck="false"
|
||||
showButton="true"
|
||||
enabledEchoCancel="true"
|
||||
@ -71,7 +69,6 @@
|
||||
uri="rtmp://HOST/video"
|
||||
dependson = "UsersModule"
|
||||
baseTabIndex="401"
|
||||
presenterShareOnly = "false"
|
||||
controlsForPresenter = "false"
|
||||
autoStart = "false"
|
||||
skipCamSettingsCheck="false"
|
||||
|
@ -43,7 +43,7 @@
|
||||
var flashvars = {};
|
||||
var params = {};
|
||||
params.quality = "high";
|
||||
params.bgcolor = "#869ca7";
|
||||
params.bgcolor = "#FFFFFF";
|
||||
params.allowfullscreen = "true";
|
||||
params.allowfullscreeninteractive = "true";
|
||||
|
||||
@ -76,7 +76,7 @@
|
||||
var fillContent = function(){
|
||||
var content = document.getElementById("content");
|
||||
if (content) {
|
||||
content.innerHTML = '<object type="application/x-shockwave-flash" id="BigBlueButton" name="BigBlueButton" tabindex="0" data="BigBlueButton.swf?v=VERSION" style="position: relative; top: 0.5px;" width="100%" height="100%" align="middle"><param name="quality" value="high"><param name="bgcolor" value="#869ca7"><param name="allowfullscreen" value="true"><param name="wmode" value="window"><param name="allowscriptaccess" value="true"><param name="seamlesstabbing" value="true"></object>';
|
||||
content.innerHTML = '<object type="application/x-shockwave-flash" id="BigBlueButton" name="BigBlueButton" tabindex="0" data="BigBlueButton.swf?v=VERSION" style="position: relative; top: 0.5px;" width="100%" height="100%" align="middle"><param name="quality" value="high"><param name="bgcolor" value="#FFFFFF"><param name="allowfullscreen" value="true"><param name="wmode" value="window"><param name="allowscriptaccess" value="true"><param name="seamlesstabbing" value="true"></object>';
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@ -197,7 +197,7 @@
|
||||
<button id="enterFlash" type="button" class="visually-hidden" onclick="startFlashFocus();">Set focus to client</button>
|
||||
<div id="content">
|
||||
<div id="altFlash" style="width:50%; margin-left: auto; margin-right: auto; ">
|
||||
<h3>You need Adobe Flash installed and enabled in order to use this client.</h3>
|
||||
You need Adobe Flash installed and enabled in order to use this client.
|
||||
<br/>
|
||||
<div style="width:50%; margin-left: auto; margin-right: auto; ">
|
||||
<a href="http://www.adobe.com/go/getflashplayer">
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" ?>
|
||||
<locales>
|
||||
<locale code="ar_SY" name="Arabic (Syria)"/>
|
||||
<locale code="ar" name="Arabic"/>
|
||||
<locale code="az_AZ" name="Azerbaijani"/>
|
||||
<locale code="eu_EU" name="Basque" />
|
||||
<locale code="bn_BN" name="Bengali" />
|
||||
@ -50,5 +50,5 @@
|
||||
<locale code="tr_TR" name="Turkish"/>
|
||||
<locale code="uk_UA" name="Ukrainian"/>
|
||||
<locale code="vi_VN" name="Vietnamese"/>
|
||||
<locale code="cy_GB" name="Welsh"/>
|
||||
<locale code="cy_GB" name="Welsh"/>
|
||||
</locales>
|
||||
|
@ -23,11 +23,16 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
xmlns:views="*"
|
||||
pageTitle="BigBlueButton"
|
||||
layout="absolute"
|
||||
preinitialize="LogUtil.initLogging(true)"
|
||||
preinitialize="init()"
|
||||
preloader="org.bigbluebutton.main.model.BigBlueButtonPreloader">
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import org.bigbluebutton.common.LogUtil;
|
||||
|
||||
private function init():void {
|
||||
LogUtil.initLogging(true);
|
||||
setStyle('backgroundColor', '0xFFFFFF');
|
||||
}
|
||||
]]>
|
||||
</mx:Script>
|
||||
|
||||
|
@ -69,11 +69,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
return _attributes.userrole as String;
|
||||
}
|
||||
|
||||
public function get presenterShareOnly():Boolean{
|
||||
if (_attributes.presenterShareOnly == "true") return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
public function start(attributes:Object):void {
|
||||
LOGGER.debug("Starting Video Module");
|
||||
_attributes = attributes;
|
||||
|
@ -26,10 +26,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
initialize="init()"
|
||||
styleName="micSettingsWindowStyle"
|
||||
showCloseButton="false">
|
||||
|
||||
<mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="handleBecomePresenter" />
|
||||
<mate:Listener type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}" method="handleBecomeViewer" />
|
||||
|
||||
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import com.asfusion.mate.events.Dispatcher;
|
||||
@ -37,10 +34,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
import org.as3commons.logging.api.ILogger;
|
||||
import org.as3commons.logging.api.getClassLogger;
|
||||
import org.bigbluebutton.core.PopUpUtil;
|
||||
import org.bigbluebutton.core.UsersUtil;
|
||||
import org.bigbluebutton.core.managers.UserManager;
|
||||
import org.bigbluebutton.main.api.JSAPI;
|
||||
import org.bigbluebutton.main.events.MadePresenterEvent;
|
||||
import org.bigbluebutton.main.model.users.Conference;
|
||||
import org.bigbluebutton.modules.phone.PhoneOptions;
|
||||
import org.bigbluebutton.modules.phone.events.AudioSelectionWindowEvent;
|
||||
@ -57,14 +52,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
browserInfo = JSAPI.getInstance().getBrowserInfo();
|
||||
|
||||
var conference:Conference = UserManager.getInstance().getConference();
|
||||
|
||||
if (!phoneOptions.listenOnlyMode) btnListenOnly.enabled = false;
|
||||
if (
|
||||
(phoneOptions.presenterShareOnly && !UsersUtil.amIPresenter() && !UsersUtil.amIModerator())
|
||||
) {
|
||||
btnMicrophone.enabled = false;
|
||||
}
|
||||
|
||||
if (!phoneOptions.listenOnlyMode)
|
||||
btnListenOnly.enabled = false;
|
||||
|
||||
if (phoneOptions.showPhoneOption) {
|
||||
txtPhone.text=ResourceUtil.getInstance().getString('bbb.audioSelection.txtPhone.text', [conference.dialNumber, conference.voiceBridge]);
|
||||
} else {
|
||||
@ -123,14 +113,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
PopUpUtil.removePopUp(this);
|
||||
}
|
||||
|
||||
private function handleBecomePresenter(e:MadePresenterEvent):void {
|
||||
if (phoneOptions.presenterShareOnly) btnMicrophone.enabled = true;
|
||||
}
|
||||
|
||||
private function handleBecomeViewer(e:MadePresenterEvent):void {
|
||||
if (phoneOptions.presenterShareOnly && !UsersUtil.amIModerator()) btnMicrophone.enabled = false;
|
||||
}
|
||||
]]>
|
||||
</mx:Script>
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
@ -19,7 +19,7 @@
|
||||
package org.bigbluebutton.modules.phone
|
||||
{
|
||||
import org.bigbluebutton.core.BBB;
|
||||
|
||||
|
||||
public class PhoneOptions {
|
||||
static public var firstAudioJoin:Boolean = true;
|
||||
|
||||
@ -30,13 +30,13 @@ package org.bigbluebutton.modules.phone
|
||||
|
||||
[Bindable]
|
||||
public var autoJoin:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var skipCheck:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var enabledEchoCancel:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var useWebRTCIfAvailable:Boolean = true;
|
||||
|
||||
@ -46,9 +46,6 @@ package org.bigbluebutton.modules.phone
|
||||
[Bindable]
|
||||
public var listenOnlyMode:Boolean = true;
|
||||
|
||||
[Bindable]
|
||||
public var presenterShareOnly:Boolean = false;
|
||||
|
||||
[Bindable]
|
||||
public var showPhoneOption:Boolean = false;
|
||||
|
||||
@ -59,17 +56,17 @@ package org.bigbluebutton.modules.phone
|
||||
public var forceListenOnly:Boolean = false;
|
||||
|
||||
public var showWebRTCStats:Boolean = false;
|
||||
|
||||
|
||||
public function PhoneOptions() {
|
||||
parseOptions();
|
||||
}
|
||||
|
||||
|
||||
public function parseOptions():void {
|
||||
var vxml:XML = BBB.getConfigForModule("PhoneModule");
|
||||
if (vxml != null) {
|
||||
if (vxml.@uri != undefined) {
|
||||
uri = vxml.@uri.toString();
|
||||
}
|
||||
if (vxml.@uri != undefined) {
|
||||
uri = vxml.@uri.toString();
|
||||
}
|
||||
if (vxml.@showButton != undefined) {
|
||||
showButton = (vxml.@showButton.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
@ -91,12 +88,6 @@ package org.bigbluebutton.modules.phone
|
||||
if (vxml.@listenOnlyMode != undefined) {
|
||||
listenOnlyMode = (vxml.@listenOnlyMode.toString().toUpperCase() == "TRUE");
|
||||
}
|
||||
if (vxml.@forceListenOnly != undefined) {
|
||||
forceListenOnly = (vxml.@forceListenOnly.toString().toUpperCase() == "TRUE");
|
||||
}
|
||||
if (vxml.@presenterShareOnly != undefined) {
|
||||
presenterShareOnly = (vxml.@presenterShareOnly.toString().toUpperCase() == "TRUE");
|
||||
}
|
||||
if (vxml.@showPhoneOption != undefined) {
|
||||
showPhoneOption = (vxml.@showPhoneOption.toString().toUpperCase() == "TRUE");
|
||||
}
|
||||
@ -109,6 +100,6 @@ package org.bigbluebutton.modules.phone
|
||||
showWebRTCStats = (vxml.@showWebRTCStats.toString().toUpperCase() == "TRUE");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,6 @@
|
||||
import org.bigbluebutton.common.Media;
|
||||
import org.bigbluebutton.core.UsersUtil;
|
||||
import org.bigbluebutton.core.events.VoiceConfEvent;
|
||||
import org.bigbluebutton.main.api.JSLog;
|
||||
import org.bigbluebutton.modules.phone.PhoneOptions;
|
||||
import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent;
|
||||
import org.bigbluebutton.modules.phone.events.FlashCallDisconnectedEvent;
|
||||
@ -388,20 +387,7 @@
|
||||
}
|
||||
hangup();
|
||||
}
|
||||
|
||||
public function handleBecomeViewer():void {
|
||||
LOGGER.debug("Handling BecomeViewer, current state: {0}, using flash: {1}", [state, usingFlash]);
|
||||
if (options.presenterShareOnly) {
|
||||
if (!usingFlash || state != IN_CONFERENCE || UsersUtil.amIModerator()) return;
|
||||
LOGGER.debug("handleBecomeViewer leaving flash with mic and joining listen only stream");
|
||||
hangup();
|
||||
|
||||
var command:JoinVoiceConferenceCommand = new JoinVoiceConferenceCommand();
|
||||
command.mic = false;
|
||||
dispatcher.dispatchEvent(command);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function handleFlashVoiceConnected():void {
|
||||
switch (state) {
|
||||
case JOIN_VOICE_CONFERENCE:
|
||||
|
@ -172,20 +172,6 @@ package org.bigbluebutton.modules.phone.managers
|
||||
ExternalInterface.call("leaveWebRTCVoiceConference");
|
||||
}
|
||||
|
||||
public function handleBecomeViewer():void {
|
||||
LOGGER.debug("handleBecomeViewer received");
|
||||
if (options.presenterShareOnly) {
|
||||
if (!usingWebRTC || model.state != Constants.IN_CONFERENCE || UsersUtil.amIModerator()) return;
|
||||
|
||||
LOGGER.debug("handleBecomeViewer leaving WebRTC and joining listen only stream");
|
||||
ExternalInterface.call("leaveWebRTCVoiceConference");
|
||||
|
||||
var command:JoinVoiceConferenceCommand = new JoinVoiceConferenceCommand();
|
||||
command.mic = false;
|
||||
dispatcher.dispatchEvent(command);
|
||||
}
|
||||
}
|
||||
|
||||
public function handleUseFlashModeCommand():void {
|
||||
usingWebRTC = false;
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<![CDATA[
|
||||
import mx.events.FlexEvent;
|
||||
|
||||
import org.bigbluebutton.main.events.MadePresenterEvent;
|
||||
import org.bigbluebutton.main.events.BBBEvent;
|
||||
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
|
||||
import org.bigbluebutton.modules.phone.events.FlashCallConnectedEvent;
|
||||
@ -118,10 +117,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<MethodInvoker generator="{FlashCallManager}" method="handleUseFlashModeCommand"/>
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}">
|
||||
<MethodInvoker generator="{FlashCallManager}" method="handleBecomeViewer"/>
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{BBBEvent.RECONNECT_SIP_SUCCEEDED_EVENT}">
|
||||
<MethodInvoker generator="{FlashCallManager}" method="handleReconnectSIPSucceededEvent"/>
|
||||
</EventHandlers>
|
||||
|
@ -26,8 +26,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import mx.events.FlexEvent;
|
||||
|
||||
import org.bigbluebutton.main.events.MadePresenterEvent;
|
||||
|
||||
import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent;
|
||||
import org.bigbluebutton.modules.phone.events.JoinVoiceConferenceCommand;
|
||||
import org.bigbluebutton.modules.phone.events.LeaveVoiceConferenceCommand;
|
||||
@ -116,7 +115,4 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<MethodInvoker generator="{WebRTCCallManager}" method="handleLeaveVoiceConferenceCommand"/>
|
||||
</EventHandlers>
|
||||
|
||||
<EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}">
|
||||
<MethodInvoker generator="{WebRTCCallManager}" method="handleBecomeViewer"/>
|
||||
</EventHandlers>
|
||||
</EventMap>
|
||||
|
@ -89,32 +89,30 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
var conference:Conference = UserManager.getInstance().getConference();
|
||||
var thisUser:BBBUser = conference.getMyUser();
|
||||
|
||||
return ((phoneOptions.presenterShareOnly && !UsersUtil.amIPresenter() && !UsersUtil.amIModerator())
|
||||
|| thisUser.disableMyMic);
|
||||
return thisUser.disableMyMic;
|
||||
}
|
||||
|
||||
private function get defaultListenOnlyMode():Boolean {
|
||||
return (phoneOptions.listenOnlyMode && phoneOptions.forceListenOnly);
|
||||
}
|
||||
|
||||
public function remoteClick(event:ShortcutEvent):void{
|
||||
|
||||
public function remoteClick(event:ShortcutEvent):void {
|
||||
this.selected = true;
|
||||
startPhone();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function mouseOverHandler(event:MouseEvent):void {
|
||||
if(_currentState == ACTIVE_STATE)
|
||||
this.styleName = "voiceConfInactiveButtonStyle";
|
||||
if (_currentState == ACTIVE_STATE)
|
||||
this.styleName = "voiceConfInactiveButtonStyle";
|
||||
else
|
||||
this.styleName = "voiceConfActiveButtonStyle";
|
||||
this.styleName = "voiceConfActiveButtonStyle";
|
||||
}
|
||||
|
||||
private function mouseOutHandler(event:MouseEvent):void {
|
||||
if(_currentState == ACTIVE_STATE)
|
||||
this.styleName = "voiceConfActiveButtonStyle";
|
||||
if (_currentState == ACTIVE_STATE)
|
||||
this.styleName = "voiceConfActiveButtonStyle";
|
||||
else
|
||||
this.styleName = "voiceConfDefaultButtonStyle";
|
||||
this.styleName = "voiceConfDefaultButtonStyle";
|
||||
}
|
||||
|
||||
private function joinAudio():void {
|
||||
@ -135,16 +133,30 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
LOGGER.debug("Sending Leave Conference command");
|
||||
dispatcher.dispatchEvent(new LeaveVoiceConferenceCommand());
|
||||
}
|
||||
|
||||
|
||||
private function onCreationComplete():void {
|
||||
var conference:Conference = UserManager.getInstance().getConference();
|
||||
var thisUser:BBBUser = conference.getMyUser();
|
||||
|
||||
// when the button is added to the stage display the audio selection window if auto join is true
|
||||
if (phoneOptions.autoJoin) {
|
||||
joinAudio();
|
||||
} else {
|
||||
joinDefaultListenOnlyMode();
|
||||
if (phoneOptions.skipCheck || thisUser.disableMyMic) {
|
||||
var command:JoinVoiceConferenceCommand = new JoinVoiceConferenceCommand();
|
||||
if (thisUser.disableMyMic) {
|
||||
command.mic = false;
|
||||
} else {
|
||||
command.mic = true;
|
||||
}
|
||||
|
||||
dispatcher.dispatchEvent(command);
|
||||
} else {
|
||||
LOGGER.debug("Sending Show Audio Selection command");
|
||||
dispatcher.dispatchEvent(new AudioSelectionWindowEvent(AudioSelectionWindowEvent.SHOW_AUDIO_SELECTION));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function onUserJoinedConference():void {
|
||||
PhoneOptions.firstAudioJoin = false;
|
||||
|
||||
|
@ -145,15 +145,6 @@ package org.bigbluebutton.modules.videoconf.maps
|
||||
|
||||
private function displayToolbarButton():void {
|
||||
button.isPresenter = true;
|
||||
|
||||
if (options.presenterShareOnly) {
|
||||
if (UsersUtil.amIPresenter()) {
|
||||
button.isPresenter = true;
|
||||
} else {
|
||||
button.isPresenter = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function addToolbarButton():void{
|
||||
@ -457,9 +448,6 @@ package org.bigbluebutton.modules.videoconf.maps
|
||||
if (options.showButton){
|
||||
LOGGER.debug("****************** Switching to viewer. Show video button?=[{0}]", [UsersUtil.amIPresenter()]);
|
||||
displayToolbarButton();
|
||||
if (_myCamSettings.length > 0 && options.presenterShareOnly) {
|
||||
stopBroadcasting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
@ -18,85 +18,81 @@
|
||||
*/
|
||||
package org.bigbluebutton.modules.videoconf.model
|
||||
{
|
||||
import flash.external.ExternalInterface;
|
||||
|
||||
import org.bigbluebutton.core.BBB;
|
||||
import org.bigbluebutton.main.api.JSAPI;
|
||||
|
||||
public class VideoConfOptions
|
||||
{
|
||||
|
||||
public class VideoConfOptions {
|
||||
public var uri:String = "rtmp://localhost/video";
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var autoStart:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var showCloseButton:Boolean = true;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var showButton:Boolean = true;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var publishWindowVisible:Boolean = true;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var viewerWindowMaxed:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var viewerWindowLocation:String = "middle";
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var smoothVideo:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var applyConvolutionFilter:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var convolutionFilter:Array = [-1, 0, -1, 0, 6, 0, -1, 0, -1];
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var filterBias:Number = 0;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var filterDivisor:Number = 4;
|
||||
|
||||
[Bindable] public var baseTabIndex:int;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var presenterShareOnly:Boolean = false;
|
||||
|
||||
public var baseTabIndex:int;
|
||||
|
||||
[Bindable]
|
||||
public var controlsForPresenter:Boolean = false;
|
||||
|
||||
public var controlsForPresenter:Boolean = false;
|
||||
|
||||
[Bindable]
|
||||
public var displayAvatar:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var focusTalking:Boolean = false;
|
||||
|
||||
[Bindable]
|
||||
public var skipCamSettingsCheck:Boolean = false;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var skipCamSettingsCheck:Boolean = false;
|
||||
|
||||
[Bindable]
|
||||
public var glowColor:String = "0x4A931D";
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var glowBlurSize:Number = 30.0;
|
||||
[Bindable]
|
||||
public var priorityRatio:Number = 2/3;
|
||||
|
||||
|
||||
[Bindable]
|
||||
public var priorityRatio:Number = 2 / 3;
|
||||
|
||||
public function VideoConfOptions() {
|
||||
parseOptions();
|
||||
}
|
||||
|
||||
|
||||
public function parseOptions():void {
|
||||
var browserInfo : Array = JSAPI.getInstance().getBrowserInfo();
|
||||
|
||||
var browserInfo:Array = JSAPI.getInstance().getBrowserInfo();
|
||||
|
||||
var vxml:XML = BBB.getConfigForModule("VideoconfModule");
|
||||
if (vxml != null) {
|
||||
if (vxml.@uri != undefined) {
|
||||
uri = vxml.@uri.toString();
|
||||
}
|
||||
}
|
||||
if (vxml.@showCloseButton != undefined) {
|
||||
showCloseButton = (vxml.@showCloseButton.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
@ -112,25 +108,22 @@ package org.bigbluebutton.modules.videoconf.model
|
||||
}
|
||||
if (vxml.@publishWindowVisible != undefined) {
|
||||
publishWindowVisible = (vxml.@publishWindowVisible.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@presenterShareOnly != undefined) {
|
||||
presenterShareOnly = (vxml.@presenterShareOnly.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@controlsForPresenter != undefined) {
|
||||
controlsForPresenter = (vxml.@controlsForPresenter.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
}
|
||||
if (vxml.@viewerWindowMaxed != undefined) {
|
||||
viewerWindowMaxed = (vxml.@viewerWindowMaxed.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@skipCamSettingsCheck != undefined) {
|
||||
skipCamSettingsCheck = (vxml.@skipCamSettingsCheck.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
}
|
||||
if (vxml.@skipCamSettingsCheck != undefined) {
|
||||
skipCamSettingsCheck = (vxml.@skipCamSettingsCheck.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
if (vxml.@viewerWindowLocation != undefined) {
|
||||
viewerWindowLocation = vxml.@viewerWindowLocation.toString().toUpperCase();
|
||||
}
|
||||
}
|
||||
if (vxml.@viewerWindowLocation != undefined) {
|
||||
viewerWindowLocation = vxml.@viewerWindowLocation.toString().toUpperCase();
|
||||
}
|
||||
}
|
||||
if (vxml.@smoothVideo != undefined) {
|
||||
smoothVideo = (vxml.@smoothVideo.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
@ -140,41 +133,40 @@ package org.bigbluebutton.modules.videoconf.model
|
||||
if (vxml.@convolutionFilter != undefined) {
|
||||
var f:Array = vxml.@convolutionFilter.split(",");
|
||||
var fint:Array = new Array();
|
||||
for (var i:int=0; i < f.length; i++) {
|
||||
for (var i:int = 0; i < f.length; i++) {
|
||||
convolutionFilter[i] = Number(f[i]);
|
||||
}
|
||||
}
|
||||
if (vxml.@filterBias != undefined) {
|
||||
filterBias = Number(vxml.@filterBias.toString());
|
||||
}
|
||||
}
|
||||
if (vxml.@filterDivisor != undefined) {
|
||||
filterDivisor = Number(vxml.@filterDivisor.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (vxml.@baseTabIndex != undefined) {
|
||||
baseTabIndex = vxml.@baseTabIndex;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
baseTabIndex = 101;
|
||||
}
|
||||
|
||||
|
||||
if (vxml.@displayAvatar != undefined) {
|
||||
displayAvatar = (vxml.@displayAvatar.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
|
||||
|
||||
if (vxml.@focusTalking != undefined) {
|
||||
focusTalking = (vxml.@focusTalking.toString().toUpperCase() == "TRUE") ? true : false;
|
||||
}
|
||||
|
||||
|
||||
if (vxml.@glowColor != undefined) {
|
||||
glowColor = vxml.@glowColor.toString();
|
||||
}
|
||||
|
||||
|
||||
if (vxml.@glowBlurSize != undefined) {
|
||||
glowBlurSize = Number(vxml.@glowBlurSize.toString());
|
||||
}
|
||||
}
|
||||
if (vxml.@priorityRatio != undefined) {
|
||||
priorityRatio = Number(vxml.@priorityRatio.toString());
|
||||
priorityRatio = Number(vxml.@priorityRatio.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
BIGBLUEBUTTON_RELEASE=1.1.0-RC
|
||||
BIGBLUEBUTTON_RELEASE=1.1.0
|
||||
|
@ -262,7 +262,7 @@
|
||||
<div class="row">
|
||||
<div class="span twelve center">
|
||||
<p>Copyright © 2017 BigBlueButton Inc.<br>
|
||||
<small>Version <a href="http://docs.bigbluebutton.org/">1.1.0-RC</a></small>
|
||||
<small>Version <a href="http://docs.bigbluebutton.org/">1.1.0</a></small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,7 +43,7 @@ export default function addShape(meetingId, whiteboardId, shape) {
|
||||
'shape.shape.id': shape.shape.id,
|
||||
'shape.shape.y': shape.shape.y,
|
||||
'shape.shape.calcedFontSize': shape.shape.calcedFontSize,
|
||||
'shape.shape.text': shape.shape.text,
|
||||
'shape.shape.text': shape.shape.text.replace(/[\r]/g, '\n'),
|
||||
});
|
||||
break;
|
||||
|
||||
|
6
bigbluebutton-html5/imports/api/users/server/handlers/lockedStatusChange.js
Normal file → Executable file
6
bigbluebutton-html5/imports/api/users/server/handlers/lockedStatusChange.js
Normal file → Executable file
@ -7,11 +7,11 @@ import muteToggle from '../methods/muteToggle';
|
||||
export default function handleLockedStatusChange({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid;
|
||||
const isLocked = arg.payload.locked;
|
||||
const isLocked = payload.locked;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(isLocked, String);
|
||||
check(isLocked, Boolean);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
@ -50,7 +50,7 @@ export default function handleLockedStatusChange({ payload }) {
|
||||
muteToggle(credentials, userId, true);
|
||||
}
|
||||
|
||||
return Logger.info(`Assigned locked status '${isLocked ? 'locked' : 'unlocked'}' id=${newPresenterId} meeting=${meetingId}`);
|
||||
return Logger.info(`Assigned locked status '${isLocked ? 'locked' : 'unlocked'}' id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default function userLeaving(credentials, userId) {
|
||||
const User = Users.findOne(selector);
|
||||
if (!User) {
|
||||
throw new Meteor.Error(
|
||||
'user-not-found', `You need a valid user to be able to toggle audio`);
|
||||
'user-not-found', `Could not find ${userId} in ${meetingId}: cannot complete userLeaving`);
|
||||
}
|
||||
|
||||
if (User.user.connection_status === OFFLINE_CONNECTION_STATUS) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import userLeaving from './userLeaving';
|
||||
|
||||
@ -10,5 +11,9 @@ export default function userLogout(credentials) {
|
||||
|
||||
const { requesterUserId } = credentials;
|
||||
|
||||
return userLeaving(credentials, requesterUserId);
|
||||
try {
|
||||
userLeaving(credentials, requesterUserId);
|
||||
} catch(e) {
|
||||
Logger.error(`Exception while executing userLeaving: ${e}`);
|
||||
}
|
||||
};
|
||||
|
@ -40,7 +40,11 @@ Meteor.publish('users', function (credentials) {
|
||||
}
|
||||
|
||||
this.onStop(() => {
|
||||
userLeaving(credentials, requesterUserId);
|
||||
try {
|
||||
userLeaving(credentials, requesterUserId);
|
||||
} catch(e) {
|
||||
Logger.error(`Exception while executing userLeaving: ${e}`);
|
||||
}
|
||||
});
|
||||
|
||||
const selector = {
|
||||
|
@ -31,6 +31,12 @@ export function logoutRouteHandler(nextState, replace, callback) {
|
||||
};
|
||||
|
||||
export function authenticatedRouteHandler(nextState, replace, callback) {
|
||||
const credentialsSnapshot = {
|
||||
meetingId: Auth.meetingID,
|
||||
requesterUserId: Auth.userID,
|
||||
requesterToken: Auth.token,
|
||||
};
|
||||
|
||||
if (Auth.loggedIn) {
|
||||
callback();
|
||||
}
|
||||
@ -40,7 +46,16 @@ export function authenticatedRouteHandler(nextState, replace, callback) {
|
||||
Auth.authenticate()
|
||||
.then(callback)
|
||||
.catch(reason => {
|
||||
logClient('error', { error: reason, method: 'authenticatedRouteHandler' });
|
||||
logClient('error', { error: reason, method: 'authenticatedRouteHandler', credentialsSnapshot });
|
||||
|
||||
// make sure users who did not connect are not added to the meeting
|
||||
// do **not** use the custom call - it relies on expired data
|
||||
Meteor.call('userLogout', credentialsSnapshot, (error, result) => {
|
||||
if (error) {
|
||||
console.error('error');
|
||||
}
|
||||
});
|
||||
|
||||
replace({ pathname: `/error/${reason.error}` });
|
||||
callback();
|
||||
});
|
||||
|
@ -15,8 +15,9 @@ class IntlStartup extends Component {
|
||||
|
||||
this.state = {
|
||||
messages: {},
|
||||
appLocale : this.props.locale,
|
||||
};
|
||||
|
||||
|
||||
this.fetchLocalizedMessages = this.fetchLocalizedMessages.bind(this);
|
||||
}
|
||||
|
||||
@ -27,7 +28,14 @@ class IntlStartup extends Component {
|
||||
|
||||
baseControls.updateLoadingState(true);
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
this.setState({appLocale: 'en'});
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(messages => {
|
||||
this.setState({ messages }, () => {
|
||||
baseControls.updateLoadingState(false);
|
||||
@ -40,18 +48,19 @@ class IntlStartup extends Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.fetchLocalizedMessages(this.props.locale);
|
||||
this.fetchLocalizedMessages(this.state.appLocale);
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
if (this.props.locale !== nextProps.locale) {
|
||||
this.setState({appLocale: nextProps.locale});
|
||||
this.fetchLocalizedMessages(nextProps.locale);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<IntlProvider locale={this.props.locale} messages={this.state.messages}>
|
||||
<IntlProvider locale={this.state.appLocale} messages={this.state.messages}>
|
||||
{this.props.children}
|
||||
</IntlProvider>
|
||||
);
|
||||
|
@ -24,9 +24,8 @@ WebApp.connectHandlers.use('/locale', (req, res) => {
|
||||
let defaultLocale = APP_CONFIG.defaultLocale;
|
||||
let localeRegion = req.query.locale.split('-');
|
||||
let messages = {};
|
||||
|
||||
let locales = [defaultLocale, localeRegion[0]];
|
||||
|
||||
let statusCode = 200;
|
||||
if (localeRegion.length > 1) {
|
||||
locales.push(`${localeRegion[0]}_${localeRegion[1].toUpperCase()}`);
|
||||
}
|
||||
@ -36,14 +35,15 @@ WebApp.connectHandlers.use('/locale', (req, res) => {
|
||||
const data = Assets.getText(`locales/${locale}.json`);
|
||||
messages = Object.assign(messages, JSON.parse(data));
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
// We dont really care about those errors since they will be a parse error
|
||||
// or a file not found which is ok
|
||||
//Variant Also Negotiates Status-Code, to alert the client that we
|
||||
//do not support the following lang.
|
||||
//https://en.wikipedia.org/wiki/Content_negotiation
|
||||
statusCode = 506;
|
||||
}
|
||||
});
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.writeHead(200);
|
||||
res.writeHead(statusCode);
|
||||
res.end(JSON.stringify(messages));
|
||||
});
|
||||
|
||||
|
@ -129,12 +129,23 @@ export function isAllowedTo(action, credentials) {
|
||||
});
|
||||
|
||||
const allowedToInitiateRequest =
|
||||
null != user &&
|
||||
authToken === user.authToken &&
|
||||
user &&
|
||||
user.authToken === authToken &&
|
||||
user.validated &&
|
||||
user.user.connection_status === 'online' &&
|
||||
'HTML5' === user.clientType &&
|
||||
null != user.user;
|
||||
user.clientType === 'HTML5' &&
|
||||
user.user &&
|
||||
user.user.connection_status === 'online';
|
||||
|
||||
const listOfSafeActions = [
|
||||
'logoutSelf',
|
||||
];
|
||||
|
||||
const requestIsSafe = listOfSafeActions.includes(action);
|
||||
|
||||
if (requestIsSafe) {
|
||||
logger.info(`permissions: requestIsSafe for ${action} by userId=${userId} allowed`);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowedToInitiateRequest) {
|
||||
let result = false;
|
||||
|
@ -18,6 +18,7 @@ const propTypes = {
|
||||
class EmojiMenu extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -28,8 +29,8 @@ class EmojiMenu extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Dropdown autoFocus={true}>
|
||||
<DropdownTrigger placeInTabOrder={true}>
|
||||
<Button
|
||||
role="button"
|
||||
label={intl.formatMessage(intlMessages.statusTriggerLabel)}
|
||||
@ -46,7 +47,7 @@ class EmojiMenu extends Component {
|
||||
// even after the DropdownTrigger inject an onClick handler
|
||||
onClick={() => null}>
|
||||
<div id="currentStatus" hidden>
|
||||
{intl.formatMessage(intlMessages.currentStatusDesc, { status: userEmojiStatus}) }
|
||||
{intl.formatMessage(intlMessages.currentStatusDesc, { 0: userEmojiStatus}) }
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
@ -57,54 +58,63 @@ class EmojiMenu extends Component {
|
||||
label={intl.formatMessage(intlMessages.raiseLabel)}
|
||||
description={intl.formatMessage(intlMessages.raiseDesc)}
|
||||
onClick={() => actions.setEmojiHandler('raiseHand')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="happy"
|
||||
label={intl.formatMessage(intlMessages.happyLabel)}
|
||||
description={intl.formatMessage(intlMessages.happyDesc)}
|
||||
onClick={() => actions.setEmojiHandler('happy')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="undecided"
|
||||
label={intl.formatMessage(intlMessages.undecidedLabel)}
|
||||
description={intl.formatMessage(intlMessages.undecidedDesc)}
|
||||
onClick={() => actions.setEmojiHandler('neutral')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="sad"
|
||||
label={intl.formatMessage(intlMessages.sadLabel)}
|
||||
description={intl.formatMessage(intlMessages.sadDesc)}
|
||||
onClick={() => actions.setEmojiHandler('sad')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="confused"
|
||||
label={intl.formatMessage(intlMessages.confusedLabel)}
|
||||
description={intl.formatMessage(intlMessages.confusedDesc)}
|
||||
onClick={() => actions.setEmojiHandler('confused')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="time"
|
||||
label={intl.formatMessage(intlMessages.awayLabel)}
|
||||
description={intl.formatMessage(intlMessages.awayDesc)}
|
||||
onClick={() => actions.setEmojiHandler('away')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="thumbs_up"
|
||||
label={intl.formatMessage(intlMessages.thumbsupLabel)}
|
||||
description={intl.formatMessage(intlMessages.thumbsupDesc)}
|
||||
onClick={() => actions.setEmojiHandler('thumbsUp')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="thumbs_down"
|
||||
label={intl.formatMessage(intlMessages.thumbsdownLabel)}
|
||||
description={intl.formatMessage(intlMessages.thumbsdownDesc)}
|
||||
onClick={() => actions.setEmojiHandler('thumbsDown')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="applause"
|
||||
label={intl.formatMessage(intlMessages.applauseLabel)}
|
||||
description={intl.formatMessage(intlMessages.applauseDesc)}
|
||||
onClick={() => actions.setEmojiHandler('applause')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListSeparator />
|
||||
<DropdownListItem
|
||||
@ -112,6 +122,7 @@ class EmojiMenu extends Component {
|
||||
label={intl.formatMessage(intlMessages.clearLabel)}
|
||||
description={intl.formatMessage(intlMessages.clearDesc)}
|
||||
onClick={() => actions.setEmojiHandler('none')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
|
@ -33,7 +33,6 @@ const intlMessages = defineMessages({
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
init: PropTypes.func.isRequired,
|
||||
fontSize: PropTypes.string,
|
||||
navbar: PropTypes.element,
|
||||
sidebar: PropTypes.element,
|
||||
@ -52,8 +51,6 @@ class App extends Component {
|
||||
this.state = {
|
||||
compactUserList: false, //TODO: Change this on userlist resize (?)
|
||||
};
|
||||
|
||||
props.init.call(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -31,7 +31,6 @@ const intlMessages = defineMessages({
|
||||
kickedMessage: {
|
||||
id: 'app.error.kicked',
|
||||
description: 'Message when the user is kicked out of the meeting',
|
||||
defaultMessage: 'You have been kicked out of the meeting',
|
||||
},
|
||||
});
|
||||
|
||||
@ -72,16 +71,7 @@ export default withRouter(injectIntl(withModalMounter(createContainer((
|
||||
},
|
||||
});
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
|
||||
const init = () => {
|
||||
if (APP_CONFIG.autoJoinAudio) {
|
||||
mountModal(<AudioModalContainer />);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
init,
|
||||
sidebar: getCaptionsStatus() ? <ClosedCaptionsContainer /> : null,
|
||||
fontSize: getFontSize(),
|
||||
};
|
||||
|
@ -155,7 +155,7 @@ $bars-padding: $lg-padding-x - .45rem; // -.45 so user-list and chat title is al
|
||||
@include mq($small-only) {
|
||||
padding-bottom: $actionsbar-height;
|
||||
margin-bottom: $actionsbar-height;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,16 +16,17 @@ class AudioContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
let didMountedAutoJoin = false;
|
||||
|
||||
export default withModalMounter(createContainer(({ mountModal }) => {
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
Service.init();
|
||||
|
||||
if (APP_CONFIG.autoJoinAudio) {
|
||||
mountModal(<AudioModal handleJoinListenOnly={Service.joinListenOnly} />);
|
||||
}
|
||||
if (didMountedAutoJoin) return;
|
||||
mountModal(<AudioModal handleJoinListenOnly={Service.joinListenOnly} />);
|
||||
didMountedAutoJoin = true;
|
||||
},
|
||||
};
|
||||
}, AudioContainer));
|
||||
|
@ -47,7 +47,7 @@ class Chat extends Component {
|
||||
<Link
|
||||
to="/users"
|
||||
role="button"
|
||||
aria-label={intl.formatMessage(intlMessages.hideChatLabel, { title: title })}>
|
||||
aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })}>
|
||||
<Icon iconName="left_arrow"/> {title}
|
||||
</Link>
|
||||
</div>
|
||||
@ -58,7 +58,7 @@ class Chat extends Component {
|
||||
<Link
|
||||
to="/users"
|
||||
role="button"
|
||||
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { title: title })}>
|
||||
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}>
|
||||
<Icon iconName="close" onClick={() => actions.handleClosePrivateChat(chatID)}/>
|
||||
</Link>)
|
||||
}
|
||||
|
@ -11,17 +11,14 @@ import ChatService from './service';
|
||||
const intlMessages = defineMessages({
|
||||
titlePublic: {
|
||||
id: 'app.chat.titlePublic',
|
||||
defaultMessage: 'Public Chat',
|
||||
description: 'Public chat title',
|
||||
},
|
||||
titlePrivate: {
|
||||
id: 'app.chat.titlePrivate',
|
||||
defaultMessage: 'Private Chat with {name}',
|
||||
description: 'Private chat title',
|
||||
},
|
||||
partnerDisconnected: {
|
||||
id: 'app.chat.partnerDisconnected',
|
||||
defaultMessage: '{name} has left the meeting',
|
||||
description: 'System chat message when the private chat partnet disconnect from the meeting',
|
||||
},
|
||||
});
|
||||
@ -65,7 +62,7 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
||||
let userMessage = messages.find(m => m.sender !== null);
|
||||
let user = ChatService.getUser(chatID, '{{NAME}}');
|
||||
|
||||
title = intl.formatMessage(intlMessages.titlePrivate, { name: user.name });
|
||||
title = intl.formatMessage(intlMessages.titlePrivate, { 0: user.name });
|
||||
chatName = user.name;
|
||||
|
||||
if (!user.isOnline) {
|
||||
@ -75,7 +72,7 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
||||
id,
|
||||
content: [{
|
||||
id,
|
||||
text: intl.formatMessage(intlMessages.partnerDisconnected, { name: user.name }),
|
||||
text: intl.formatMessage(intlMessages.partnerDisconnected, { 0: user.name }),
|
||||
time,
|
||||
},],
|
||||
time,
|
||||
|
@ -105,9 +105,9 @@ class MessageForm extends Component {
|
||||
<TextareaAutosize
|
||||
className={styles.input}
|
||||
id="message-input"
|
||||
placeholder={intl.formatMessage(messages.inputPlaceholder, { name: chatName })}
|
||||
placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: chatName })}
|
||||
aria-controls={this.props.chatAreaId}
|
||||
aria-label={intl.formatMessage(messages.inputLabel, { name: chatTitle })}
|
||||
aria-label={intl.formatMessage(messages.inputLabel, { 0: chatTitle })}
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="true"
|
||||
|
@ -11,26 +11,28 @@ export default class ClosedCaptions extends React.Component {
|
||||
|
||||
renderCaptions(caption) {
|
||||
let text = caption.captions;
|
||||
const captionStyles = {
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
fontFamily: this.props.fontFamily,
|
||||
fontSize: this.props.fontSize,
|
||||
color: this.props.fontColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', fontFamily: this.props.fontFamily, fontSize: this.props.fontSize, color: this.props.fontColor }}
|
||||
style={captionStyles}
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
key={caption.index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { ccScrollArea } = this.refs;
|
||||
|
||||
var node = findDOMNode(ccScrollArea);
|
||||
node.scrollTop = node.scrollHeight;
|
||||
}
|
||||
|
||||
componentWillUpdate() {
|
||||
const { ccScrollArea } = this.refs;
|
||||
|
||||
var node = findDOMNode(ccScrollArea);
|
||||
const node = findDOMNode(ccScrollArea);
|
||||
|
||||
// number 4 is for the border
|
||||
// offset height includes the border, but scrollheight doesn't
|
||||
this.shouldScrollBottom = node.scrollTop + node.offsetHeight - 4 === node.scrollHeight;
|
||||
@ -39,22 +41,28 @@ export default class ClosedCaptions extends React.Component {
|
||||
componentDidUpdate() {
|
||||
if (this.shouldScrollBottom) {
|
||||
const { ccScrollArea } = this.refs;
|
||||
var node = findDOMNode(ccScrollArea);
|
||||
node.scrollTop = node.scrollHeight
|
||||
const node = findDOMNode(ccScrollArea);
|
||||
node.scrollTop = node.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
locale,
|
||||
captions,
|
||||
backgroundColor,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div disabled className={styles.ccbox}>
|
||||
<div className={styles.title}>
|
||||
<p> {this.props.locale ? this.props.locale : 'Locale is not selected'} </p>
|
||||
<p> {locale ? locale : 'Locale is not selected'} </p>
|
||||
</div>
|
||||
<div
|
||||
ref="ccScrollArea"
|
||||
className={styles.frame}
|
||||
style={{background: this.props.backgroundColor}}>
|
||||
{this.props.captions[this.props.locale] ? this.props.captions[this.props.locale].captions.map((caption) => (
|
||||
style={{ background: backgroundColor }}>
|
||||
{captions[locale] ? captions[locale].captions.map((caption) => (
|
||||
this.renderCaptions(caption)
|
||||
)) : null }
|
||||
</div>
|
||||
|
@ -18,6 +18,5 @@ class ClosedCaptionsContainer extends Component {
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
const data = ClosedCaptionsService.getCCData();
|
||||
return data;
|
||||
return ClosedCaptionsService.getCCData();
|
||||
}, ClosedCaptionsContainer);
|
||||
|
@ -83,15 +83,18 @@ class Dropdown extends Component {
|
||||
|
||||
handleShow() {
|
||||
this.setState({ isOpen: true }, this.handleStateCallback);
|
||||
|
||||
const contentElement = findDOMNode(this.refs.content);
|
||||
contentElement.querySelector(FOCUSABLE_CHILDREN).focus();
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
|
||||
const { autoFocus } = this.props;
|
||||
|
||||
this.setState({ isOpen: false }, this.handleStateCallback);
|
||||
const triggerElement = findDOMNode(this.refs.trigger);
|
||||
triggerElement.focus();
|
||||
|
||||
if (autoFocus) {
|
||||
const triggerElement = findDOMNode(this.refs.trigger);
|
||||
triggerElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@ -122,7 +125,14 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, className, style, intl } = this.props;
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
style, intl,
|
||||
hasPopup,
|
||||
ariaLive,
|
||||
ariaRelevant,
|
||||
} = this.props;
|
||||
|
||||
let trigger = children.find(x => x.type === DropdownTrigger);
|
||||
let content = children.find(x => x.type === DropdownContent);
|
||||
@ -143,7 +153,12 @@ class Dropdown extends Component {
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={style} className={cx(styles.dropdown, className)}>
|
||||
<div
|
||||
style={style}
|
||||
className={cx(styles.dropdown, className)}
|
||||
aria-live={ariaLive}
|
||||
aria-relevant={ariaRelevant}
|
||||
aria-haspopup={hasPopup}>
|
||||
{trigger}
|
||||
{content}
|
||||
{ this.state.isOpen ?
|
||||
|
@ -78,7 +78,7 @@ export default class DropdownList extends Component {
|
||||
nextActiveItemIndex = this.childrenRefs.length - 1;
|
||||
}
|
||||
|
||||
if ([KEY_CODES.TAB, KEY_CODES.ESCAPE].includes(event.which)) {
|
||||
if ([KEY_CODES.ESCAPE].includes(event.which)) {
|
||||
nextActiveItemIndex = 0;
|
||||
dropdownHide();
|
||||
}
|
||||
|
@ -29,14 +29,16 @@ export default class DropdownListItem extends Component {
|
||||
|
||||
render() {
|
||||
const { label, description, children, injectRef, tabIndex, onClick, onKeyDown,
|
||||
className, style, separator, intl, } = this.props;
|
||||
className, style, separator, intl, placeInTabOrder, } = this.props;
|
||||
|
||||
let index = (placeInTabOrder) ? 0 : -1;
|
||||
|
||||
return (
|
||||
<li
|
||||
ref={injectRef}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
tabIndex={index}
|
||||
aria-labelledby={this.labelID}
|
||||
aria-describedby={this.descID}
|
||||
className={cx(styles.item, className)}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import styles from '../styles';
|
||||
|
||||
const propTypes = {
|
||||
description: PropTypes.string,
|
||||
};
|
||||
|
||||
export default class DropdownListTitle extends Component {
|
||||
|
||||
render() {
|
||||
const { intl, description } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<li className={styles.title} aria-describedby="labelContext">{this.props.children}</li>
|
||||
<div id="labelContext" aria-label={description}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DropdownListTitle.propTypes = propTypes;
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import styles from '../styles';
|
||||
|
||||
const propTypes = {
|
||||
description: PropTypes.string,
|
||||
};
|
||||
|
||||
export default class DropdownListTitle extends Component {
|
||||
|
||||
render() {
|
||||
const { intl, description } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<li className={styles.title} aria-describedby="labelContext">{this.props.children}</li>
|
||||
<div id="labelContext" aria-label={description}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DropdownListTitle.propTypes = propTypes;
|
||||
|
@ -41,14 +41,16 @@ export default class DropdownTrigger extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, style, className, } = this.props;
|
||||
const { children, style, className, placeInTabOrder, } = this.props;
|
||||
const TriggerComponent = React.Children.only(children);
|
||||
|
||||
let index = (placeInTabOrder) ? '0' : '-1';
|
||||
|
||||
const TriggerComponentBounded = React.cloneElement(children, {
|
||||
onClick: this.handleClick,
|
||||
onKeyDown: this.handleKeyDown,
|
||||
'aria-haspopup': true,
|
||||
tabIndex: '0',
|
||||
tabIndex: index,
|
||||
style: style,
|
||||
className: cx(children.props.className, className),
|
||||
});
|
||||
|
@ -83,8 +83,8 @@ class SettingsDropdown extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Dropdown autoFocus={true}>
|
||||
<DropdownTrigger placeInTabOrder={true}>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.optionsLabel)}
|
||||
icon="more"
|
||||
|
14
bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
Normal file → Executable file
14
bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx
Normal file → Executable file
@ -27,32 +27,26 @@ const STATUS_OFFLINE = 'offline';
|
||||
const intlMessages = defineMessages({
|
||||
failedMessage: {
|
||||
id: 'app.failedMessage',
|
||||
defaultMessage: 'Apologies, trouble connecting to the server.',
|
||||
description: 'Message when the client is trying to connect to the server',
|
||||
description: 'Notification for connecting to server problems',
|
||||
},
|
||||
connectingMessage: {
|
||||
id: 'app.connectingMessage',
|
||||
defaultMessage: 'Connecting...',
|
||||
description: 'Message when the client is trying to connect to the server',
|
||||
description: 'Notification message for when client is connecting to server',
|
||||
},
|
||||
waitingMessage: {
|
||||
id: 'app.waitingMessage',
|
||||
defaultMessage: 'Disconnected. Trying to reconnect in {seconds} seconds...',
|
||||
description: 'Message when the client is trying to reconnect to the server',
|
||||
description: 'Notification message for disconnection with reconnection counter',
|
||||
},
|
||||
breakoutTimeRemaining: {
|
||||
id: 'app.breakoutTimeRemainingMessage',
|
||||
defaultMessage: 'Breakout Room time remaining: {time}',
|
||||
description: 'Message that tells how much time is remaining for the breakout room',
|
||||
},
|
||||
breakoutWillClose: {
|
||||
id: 'app.breakoutWillCloseMessage',
|
||||
defaultMessage: 'Time ended. Breakout Room will close soon',
|
||||
description: 'Message that tells time has ended and breakout will close',
|
||||
},
|
||||
calculatingBreakoutTimeRemaining: {
|
||||
id: 'app.calculatingBreakoutTimeRemaining',
|
||||
defaultMessage: 'Calculating remaining time...',
|
||||
description: 'Message that tells that the remaining time is being calculated',
|
||||
},
|
||||
});
|
||||
@ -146,7 +140,7 @@ export default injectIntl(createContainer(({ intl }) => {
|
||||
retryInterval = startCounter(sec, setRetrySeconds, getRetrySeconds, retryInterval);
|
||||
data.message = intl.formatMessage(
|
||||
intlMessages.waitingMessage,
|
||||
{ seconds: getRetrySeconds() }
|
||||
{ 0: getRetrySeconds() }
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ export default class DefaultContent extends Component {
|
||||
<FormattedMessage
|
||||
id="app.home.greeting"
|
||||
description="Message to greet the user."
|
||||
defaultMessage="Welcome {name}! Your presentation will begin shortly..."
|
||||
values={{ name: 'James Bond' }}
|
||||
defaultMessage="Welcome {0}! Your presentation will begin shortly..."
|
||||
values={{ 0: 'James Bond' }}
|
||||
/>
|
||||
<br/>
|
||||
Today is {' '}<FormattedDate value={Date.now()} />
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import Modal from '/imports/ui/components/modal/fullscreen/component';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { withModalMounter } from '../modal/service';
|
||||
import ClosedCaptions from '/imports/ui/components/settings/submenus/closed-captions/component';
|
||||
import Application from '/imports/ui/components/settings/submenus/application/container';
|
||||
import Participants from '/imports/ui/components/settings/submenus/participants/component';
|
||||
@ -105,6 +106,7 @@ class Settings extends Component {
|
||||
title={intl.formatMessage(intlMessages.SettingsLabel)}
|
||||
confirm={{
|
||||
callback: (() => {
|
||||
this.props.mountModal(null);
|
||||
this.updateSettings(this.state.current);
|
||||
}),
|
||||
label: intl.formatMessage(intlMessages.SaveLabel),
|
||||
@ -199,4 +201,4 @@ class Settings extends Component {
|
||||
}
|
||||
|
||||
Settings.propTypes = propTypes;
|
||||
export default injectIntl(Settings);
|
||||
export default withModalMounter(injectIntl(Settings));
|
||||
|
@ -181,7 +181,7 @@ class ApplicationMenu extends BaseMenu {
|
||||
defaultValue={this.state.settings.locale}
|
||||
className={styles.select}
|
||||
onChange={this.handleSelectChange.bind(this, 'locale', availableLocales)}>
|
||||
<option>
|
||||
<option disabled={true}>
|
||||
{ availableLocales &&
|
||||
availableLocales.length ?
|
||||
intl.formatMessage(intlMessages.languageOptionLabel) :
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import styles from '../styles';
|
||||
import cx from 'classnames';
|
||||
import BaseMenu from '../base/component';
|
||||
@ -128,6 +127,7 @@ class ClosedCaptionsMenu extends BaseMenu {
|
||||
const {
|
||||
locales,
|
||||
intl,
|
||||
isModerator,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -155,210 +155,215 @@ class ClosedCaptionsMenu extends BaseMenu {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.takeOwnershipLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Checkbox
|
||||
onChange={() => this.handleToggle('takeOwnership')}
|
||||
checked={this.state.settings.takeOwnership}
|
||||
ariaLabelledBy={'takeOwnership'}
|
||||
ariaLabel={intl.formatMessage(intlMessages.takeOwnershipLabel)}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.languageLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.languageLabel)}>
|
||||
<select
|
||||
defaultValue={locales ? locales.indexOf(this.state.settings.locale) : -1}
|
||||
className={styles.select}
|
||||
onChange={this.handleSelectChange.bind(this, 'locale', this.props.locales)}>
|
||||
<option>
|
||||
{ this.props.locales &&
|
||||
this.props.locales.length ?
|
||||
intl.formatMessage(intlMessages.localeOptionLabel) :
|
||||
intl.formatMessage(intlMessages.noLocaleOptionLabel) }
|
||||
</option>
|
||||
{this.props.locales ? this.props.locales.map((locale, index) =>
|
||||
<option key={index} value={index}>
|
||||
{locale}
|
||||
</option>
|
||||
) : null }
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontFamilyLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontFamilyLabel)}>
|
||||
<select
|
||||
defaultValue={FONT_FAMILIES.indexOf(this.state.settings.fontFamily)}
|
||||
onChange={this.handleSelectChange.bind(this, 'fontFamily', FONT_FAMILIES)}
|
||||
className={styles.select}>
|
||||
<option value='-1' disabled>
|
||||
{intl.formatMessage(intlMessages.fontFamilyOptionLabel)}
|
||||
</option>
|
||||
{
|
||||
FONT_FAMILIES.map((family, index) =>
|
||||
<option key={index} value={index}>
|
||||
{family}
|
||||
{ this.state.settings.enabled ?
|
||||
<div>
|
||||
{ isModerator ?
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.takeOwnershipLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={cx(styles.formElement, styles.pullContentRight)}>
|
||||
<Checkbox
|
||||
onChange={() => this.handleToggle('takeOwnership')}
|
||||
checked={this.state.settings.takeOwnership}
|
||||
ariaLabelledBy={'takeOwnership'}
|
||||
ariaLabel={intl.formatMessage(intlMessages.takeOwnershipLabel)}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: null }
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.languageLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.languageLabel)}>
|
||||
<select
|
||||
defaultValue={locales ? locales.indexOf(this.state.settings.locale) : -1}
|
||||
className={styles.select}
|
||||
onChange={this.handleSelectChange.bind(this, 'locale', this.props.locales)}>
|
||||
<option>
|
||||
{ this.props.locales &&
|
||||
this.props.locales.length ?
|
||||
intl.formatMessage(intlMessages.localeOptionLabel) :
|
||||
intl.formatMessage(intlMessages.noLocaleOptionLabel) }
|
||||
</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontSizeLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontSizeLabel)}>
|
||||
<select
|
||||
defaultValue={FONT_SIZES.indexOf(this.state.settings.fontSize)}
|
||||
onChange={this.handleSelectChange.bind(this, 'fontSize', FONT_SIZES)}
|
||||
className={styles.select}>
|
||||
<option value='-1' disabled>
|
||||
{intl.formatMessage(intlMessages.fontSizeOptionLabel)}
|
||||
</option>
|
||||
{
|
||||
FONT_SIZES.map((size, index) =>
|
||||
{this.props.locales ? this.props.locales.map((locale, index) =>
|
||||
<option key={index} value={index}>
|
||||
{size}
|
||||
{locale}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.backgroundColorLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.backgroundColorLabel)}>
|
||||
<div
|
||||
tabIndex='0'
|
||||
className={ styles.swatch }
|
||||
onClick={
|
||||
this.handleColorPickerClick.bind(this, 'displayBackgroundColorPicker')
|
||||
}>
|
||||
<div
|
||||
className={styles.swatchInner}
|
||||
style={ { background: this.state.settings.backgroundColor } }>
|
||||
) : null }
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.displayBackgroundColorPicker ?
|
||||
<div className={styles.colorPickerPopover}>
|
||||
<div
|
||||
className={styles.colorPickerOverlay}
|
||||
onClick={ this.handleCloseColorPicker.bind(this) }>
|
||||
</div>
|
||||
<GithubPicker
|
||||
onChange={this.handleColorChange.bind(this, 'backgroundColor')}
|
||||
color={this.state.settings.backgroundColor}
|
||||
colors={COLORS}
|
||||
width={'140px'}
|
||||
triangle={'top-right'}
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontColorLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontColorLabel)}>
|
||||
<div
|
||||
tabIndex='0'
|
||||
className={ styles.swatch }
|
||||
onClick={ this.handleColorPickerClick.bind(this, 'displayFontColorPicker') }>
|
||||
<div
|
||||
className={styles.swatchInner}
|
||||
style={ { background: this.state.settings.fontColor } }>
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontFamilyLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.displayFontColorPicker ?
|
||||
<div className={styles.colorPickerPopover}>
|
||||
<div
|
||||
className={styles.colorPickerOverlay}
|
||||
onClick={ this.handleCloseColorPicker.bind(this) }
|
||||
>
|
||||
</div>
|
||||
<GithubPicker
|
||||
onChange={this.handleColorChange.bind(this, 'fontColor')}
|
||||
color={this.state.settings.fontColor}
|
||||
colors={COLORS}
|
||||
width={'140px'}
|
||||
triangle={'top-right'}
|
||||
/>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontFamilyLabel)}>
|
||||
<select
|
||||
defaultValue={FONT_FAMILIES.indexOf(this.state.settings.fontFamily)}
|
||||
onChange={this.handleSelectChange.bind(this, 'fontFamily', FONT_FAMILIES)}
|
||||
className={styles.select}>
|
||||
<option value='-1' disabled>
|
||||
{intl.formatMessage(intlMessages.fontFamilyOptionLabel)}
|
||||
</option>
|
||||
{
|
||||
FONT_FAMILIES.map((family, index) =>
|
||||
<option key={index} value={index}>
|
||||
{family}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontSizeLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontSizeLabel)}>
|
||||
<select
|
||||
defaultValue={FONT_SIZES.indexOf(this.state.settings.fontSize)}
|
||||
onChange={this.handleSelectChange.bind(this, 'fontSize', FONT_SIZES)}
|
||||
className={styles.select}>
|
||||
<option value='-1' disabled>
|
||||
{intl.formatMessage(intlMessages.fontSizeOptionLabel)}
|
||||
</option>
|
||||
{
|
||||
FONT_SIZES.map((size, index) =>
|
||||
<option key={index} value={index}>
|
||||
{size}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.backgroundColorLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.backgroundColorLabel)}>
|
||||
<div
|
||||
tabIndex='0'
|
||||
className={ styles.swatch }
|
||||
onClick={
|
||||
this.handleColorPickerClick.bind(this, 'displayBackgroundColorPicker')
|
||||
}>
|
||||
<div
|
||||
className={styles.swatchInner}
|
||||
style={ { background: this.state.settings.backgroundColor } }>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.displayBackgroundColorPicker ?
|
||||
<div className={styles.colorPickerPopover}>
|
||||
<div
|
||||
className={styles.colorPickerOverlay}
|
||||
onClick={ this.handleCloseColorPicker.bind(this) }>
|
||||
</div>
|
||||
<GithubPicker
|
||||
onChange={this.handleColorChange.bind(this, 'backgroundColor')}
|
||||
color={this.state.settings.backgroundColor}
|
||||
colors={COLORS}
|
||||
width={'140px'}
|
||||
triangle={'top-right'}
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(styles.row, styles.spacedLeft)}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.fontColorLabel)}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div
|
||||
className={cx(styles.formElement, styles.pullContentRight)}
|
||||
aria-label={intl.formatMessage(intlMessages.fontColorLabel)}>
|
||||
<div
|
||||
tabIndex='0'
|
||||
className={ styles.swatch }
|
||||
onClick={ this.handleColorPickerClick.bind(this, 'displayFontColorPicker') }>
|
||||
<div
|
||||
className={styles.swatchInner}
|
||||
style={ { background: this.state.settings.fontColor } }>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.displayFontColorPicker ?
|
||||
<div className={styles.colorPickerPopover}>
|
||||
<div
|
||||
className={styles.colorPickerOverlay}
|
||||
onClick={ this.handleCloseColorPicker.bind(this) }
|
||||
>
|
||||
</div>
|
||||
<GithubPicker
|
||||
onChange={this.handleColorChange.bind(this, 'fontColor')}
|
||||
color={this.state.settings.fontColor}
|
||||
colors={COLORS}
|
||||
width={'140px'}
|
||||
triangle={'top-right'}
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cx(styles.ccPreviewBox, styles.spacedLeft)}
|
||||
role='presentation'
|
||||
style={ { background: this.state.settings.backgroundColor } }>
|
||||
<span style={this.getPreviewStyle()}>
|
||||
Etiam porta sem malesuada magna mollis euis-mod.
|
||||
Donec ullamcorper nulla non metus auctor fringilla.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx(styles.ccPreviewBox, styles.spacedLeft)}
|
||||
role='presentation'
|
||||
style={ { background: this.state.settings.backgroundColor } }>
|
||||
<span style={this.getPreviewStyle()}>
|
||||
Etiam porta sem malesuada magna mollis euis-mod.
|
||||
Donec ullamcorper nulla non metus auctor fringilla.
|
||||
</span>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -14,6 +14,5 @@ class ClosedCaptionsMenuContainer extends Component {
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
let data = Service.getClosedCaptionSettings();
|
||||
return data;
|
||||
return Service.getClosedCaptionSettings();
|
||||
}, ClosedCaptionsMenuContainer);
|
||||
|
@ -1,22 +1,10 @@
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import Captions from '/imports/api/captions';
|
||||
|
||||
getClosedCaptionSettings = () => {
|
||||
let ccSettings = {};
|
||||
let ccEnabled = Storage.getItem('closedCaptions');
|
||||
ccSettings.ccEnabled = !!ccEnabled;
|
||||
|
||||
//list of unique locales in the Captions Collection
|
||||
let locales = _.uniq(Captions.find({}, {
|
||||
sort: { locale: 1 },
|
||||
fields: { locale: true },
|
||||
}).fetch().map(function (obj) {
|
||||
return obj.locale;
|
||||
}), true);
|
||||
|
||||
//adding the list of active locales to the closed-captions settings object
|
||||
ccSettings.locales = locales;
|
||||
|
||||
return ccSettings;
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ export default class UserAvatar extends Component {
|
||||
|
||||
return (
|
||||
<div className={user.isOnline ? styles.userAvatar : styles.userLogout}
|
||||
style={avatarStyles}>
|
||||
style={avatarStyles} aria-hidden="true">
|
||||
<div>
|
||||
{this.renderAvatarContent()}
|
||||
</div>
|
||||
|
@ -43,6 +43,7 @@ class ChatListItem extends Component {
|
||||
openChat,
|
||||
compact,
|
||||
intl,
|
||||
tabIndex,
|
||||
} = this.props;
|
||||
|
||||
const linkPath = [PRIVATE_CHAT_PATH, chat.id].join('');
|
||||
@ -57,12 +58,13 @@ class ChatListItem extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={cx(styles.chatListItem, linkClasses)}>
|
||||
<Link
|
||||
to={linkPath}
|
||||
className={styles.chatListItemLink}
|
||||
className={cx(styles.chatListItem, linkClasses)}
|
||||
role="button"
|
||||
aria-expanded={isCurrentChat}>
|
||||
aria-expanded={isCurrentChat}
|
||||
tabIndex={tabIndex}>
|
||||
<div className={styles.chatListItemLink}>
|
||||
{chat.icon ? this.renderChatIcon() : this.renderChatAvatar()}
|
||||
<div className={styles.chatName}>
|
||||
{!compact ? <span className={styles.chatNameMain}>{chat.name}</span> : null }
|
||||
@ -71,15 +73,15 @@ class ChatListItem extends Component {
|
||||
<div
|
||||
className={styles.unreadMessages}
|
||||
aria-label={isSingleMessage
|
||||
? intl.formatMessage(intlMessages.unreadSingular, { count: chat.unreadCounter })
|
||||
: intl.formatMessage(intlMessages.unreadPlural, { count: chat.unreadCounter })}>
|
||||
? intl.formatMessage(intlMessages.unreadSingular, { 0: chat.unreadCounter })
|
||||
: intl.formatMessage(intlMessages.unreadPlural, { 0: chat.unreadCounter })}>
|
||||
<div className={styles.unreadMessagesText} aria-hidden="true">
|
||||
{chat.unreadCounter}
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
@extend %list-item;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chatListItemLink {
|
||||
|
@ -6,6 +6,7 @@ import cx from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import UserListItem from './user-list-item/component.jsx';
|
||||
import ChatListItem from './chat-list-item/component.jsx';
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.array.isRequired,
|
||||
@ -30,6 +31,128 @@ class UserList extends Component {
|
||||
this.state = {
|
||||
compact: this.props.compact,
|
||||
};
|
||||
|
||||
this.rovingIndex = this.rovingIndex.bind(this);
|
||||
this.focusList = this.focusList.bind(this);
|
||||
this.focusListItem = this.focusListItem.bind(this);
|
||||
this.counter = -1;
|
||||
}
|
||||
|
||||
focusList(activeElement, list) {
|
||||
activeElement.tabIndex = -1;
|
||||
this.counter = 0;
|
||||
list.tabIndex = 0;
|
||||
list.focus();
|
||||
}
|
||||
|
||||
focusListItem(active, direction, element, count) {
|
||||
|
||||
function select() {
|
||||
element.tabIndex = 0;
|
||||
element.focus();
|
||||
}
|
||||
|
||||
active.tabIndex = -1;
|
||||
|
||||
switch (direction) {
|
||||
case 'down':
|
||||
element.childNodes[this.counter].tabIndex = 0;
|
||||
element.childNodes[this.counter].focus();
|
||||
this.counter++;
|
||||
break;
|
||||
case 'up':
|
||||
this.counter--;
|
||||
element.childNodes[this.counter].tabIndex = 0;
|
||||
element.childNodes[this.counter].focus();
|
||||
break;
|
||||
case 'upLoopUp':
|
||||
case 'upLoopDown':
|
||||
this.counter = count - 1;
|
||||
select();
|
||||
break;
|
||||
case 'downLoopDown':
|
||||
this.counter = -1;
|
||||
select();
|
||||
break;
|
||||
case 'downLoopUp':
|
||||
this.counter = 1;
|
||||
select();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rovingIndex(...Args) {
|
||||
const { users, openChats } = this.props;
|
||||
|
||||
let active = document.activeElement;
|
||||
let list;
|
||||
let items;
|
||||
let count;
|
||||
|
||||
switch (Args[1]) {
|
||||
case 'users':
|
||||
list = this._usersList;
|
||||
items = this._userItems;
|
||||
count = users.length;
|
||||
break;
|
||||
case 'messages':
|
||||
list = this._msgsList;
|
||||
items = this._msgItems;
|
||||
count = openChats.length;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ESCAPE
|
||||
|| this.counter === -1
|
||||
|| this.counter > count) {
|
||||
this.focusList(active, list);
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ENTER
|
||||
|| Args[0].keyCode === KEY_CODES.ARROW_RIGHT
|
||||
|| Args[0].keyCode === KEY_CODES.ARROW_LEFT) {
|
||||
active.firstChild.click();
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ARROW_DOWN) {
|
||||
if (this.counter < count) {
|
||||
this.focusListItem(active, 'down', items);
|
||||
}else if (this.counter === count) {
|
||||
this.focusListItem(active, 'downLoopDown', list);
|
||||
}else if (this.counter === 0) {
|
||||
this.focusListItem(active, 'downLoopUp', list);
|
||||
}
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ARROW_UP) {
|
||||
if (this.counter < count && this.counter !== 0) {
|
||||
this.focusListItem(active, 'up', items);
|
||||
}else if (this.counter === 0) {
|
||||
this.focusListItem(active, 'upLoopUp', list, count);
|
||||
}else if (this.counter === count) {
|
||||
this.focusListItem(active, 'upLoopDown', list, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let _this = this;
|
||||
|
||||
if (!this.state.compact) {
|
||||
this._msgsList.addEventListener('keypress', function (event) {
|
||||
_this.rovingIndex.call(this, event, 'messages');
|
||||
});
|
||||
|
||||
this._usersList.addEventListener('keypress', function (event) {
|
||||
_this.rovingIndex.call(this, event, 'users');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._msgsList.removeEventListener('keypress', function (event) {}, false);
|
||||
|
||||
this._usersList.removeEventListener('keypress', function (event) {}, false);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -48,9 +171,9 @@ class UserList extends Component {
|
||||
<div className={styles.header}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h2 className={styles.headerTitle}>
|
||||
<div className={styles.headerTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.participantsTitle)}
|
||||
</h2> : null
|
||||
</div> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@ -76,11 +199,14 @@ class UserList extends Component {
|
||||
<div className={styles.messages}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<div className={styles.smallTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.messagesTitle)}
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
</div> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<div className={styles.scrollableList}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
className={styles.scrollableList}
|
||||
ref={(r) => this._msgsList = r}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
@ -89,15 +215,18 @@ class UserList extends Component {
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component="ul"
|
||||
component="div"
|
||||
className={cx(styles.chatsList, styles.scrollableList)}>
|
||||
<div ref={(r) => this._msgItems = r}>
|
||||
{openChats.map(chat => (
|
||||
<ChatListItem
|
||||
compact={this.state.compact}
|
||||
key={chat.id}
|
||||
openChat={openChat}
|
||||
chat={chat} />
|
||||
chat={chat}
|
||||
tabIndex={-1} />
|
||||
))}
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
@ -111,6 +240,7 @@ class UserList extends Component {
|
||||
isBreakoutRoom,
|
||||
intl,
|
||||
makeCall,
|
||||
meeting,
|
||||
} = this.props;
|
||||
|
||||
const userActions = {
|
||||
@ -150,33 +280,41 @@ class UserList extends Component {
|
||||
<div className={styles.participants}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<div className={styles.smallTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.usersTitle)}
|
||||
({users.length})
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
</div> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component="ul"
|
||||
className={cx(styles.participantsList, styles.scrollableList)}>
|
||||
{
|
||||
users.map(user => (
|
||||
<UserListItem
|
||||
compact={this.state.compact}
|
||||
key={user.id}
|
||||
isBreakoutRoom={isBreakoutRoom}
|
||||
user={user}
|
||||
currentUser={currentUser}
|
||||
userActions={userActions}
|
||||
/>
|
||||
))}
|
||||
</ReactCSSTransitionGroup>
|
||||
<div
|
||||
className={styles.scrollableList}
|
||||
tabIndex={0}
|
||||
ref={(r) => this._usersList = r}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component="div"
|
||||
className={cx(styles.participantsList, styles.scrollableList)}>
|
||||
<div ref={(r) => this._userItems = r}>
|
||||
{
|
||||
users.map(user => (
|
||||
<UserListItem
|
||||
compact={this.state.compact}
|
||||
key={user.id}
|
||||
isBreakoutRoom={isBreakoutRoom}
|
||||
user={user}
|
||||
currentUser={currentUser}
|
||||
userActions={userActions}
|
||||
meeting={meeting}
|
||||
/>))
|
||||
}
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { createContainer } from 'meteor/react-meteor-data';
|
||||
import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import Service from './service.js';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
import UserList from './component.jsx';
|
||||
|
||||
@ -17,12 +18,14 @@ class UserListContainer extends Component {
|
||||
userActions,
|
||||
isBreakoutRoom,
|
||||
children,
|
||||
meeting,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<UserList
|
||||
compact={compact}
|
||||
users={users}
|
||||
meeting={meeting}
|
||||
currentUser={currentUser}
|
||||
openChats={openChats}
|
||||
openChat={openChat}
|
||||
@ -37,6 +40,7 @@ class UserListContainer extends Component {
|
||||
|
||||
export default createContainer(({ params }) => ({
|
||||
users: Service.getUsers(),
|
||||
meeting: Meetings.findOne({}),
|
||||
currentUser: Service.getCurrentUser(),
|
||||
openChats: Service.getOpenChats(params.chatID),
|
||||
openChat: params.chatID,
|
||||
|
@ -32,7 +32,8 @@ const mapUser = user => ({
|
||||
isListenOnly: user.listenOnly,
|
||||
isSharingWebcam: user.webcam_stream.length,
|
||||
isPhoneUser: user.phone_user,
|
||||
isOnline: user.connection_status === 'online'
|
||||
isOnline: user.connection_status === 'online',
|
||||
isLocked: user.locked,
|
||||
});
|
||||
|
||||
const mapOpenChats = chat => {
|
||||
|
@ -15,8 +15,8 @@ import DropdownContent from '/imports/ui/components/dropdown/content/component';
|
||||
import DropdownList from '/imports/ui/components/dropdown/list/component';
|
||||
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
|
||||
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
|
||||
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
|
||||
|
||||
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
|
||||
|
||||
const propTypes = {
|
||||
user: React.PropTypes.shape({
|
||||
name: React.PropTypes.string.isRequired,
|
||||
@ -46,10 +46,22 @@ const messages = defineMessages({
|
||||
id: 'app.userlist.you',
|
||||
description: 'Text for identifying your user',
|
||||
},
|
||||
locked: {
|
||||
id: 'app.userlist.locked',
|
||||
description: 'Text for identifying locked user',
|
||||
},
|
||||
menuTitleContext: {
|
||||
id: 'app.userlist.menuTitleContext',
|
||||
description: 'adds context to userListItem menu title',
|
||||
},
|
||||
userItemStatusAriaLabel: {
|
||||
id: 'app.userlist.useritem.status.arialabel',
|
||||
description: 'adds aria label for user and status',
|
||||
},
|
||||
userItemAriaLabel: {
|
||||
id: 'app.userlist.useritem.nostatus.arialabel',
|
||||
description: 'aria label for user',
|
||||
},
|
||||
});
|
||||
|
||||
const userActionsTransition = {
|
||||
@ -167,7 +179,7 @@ class UserListItem extends Component {
|
||||
if (!isDropdownVisible) {
|
||||
const offsetPageTop =
|
||||
(dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight - scrollContainer.scrollTop);
|
||||
|
||||
|
||||
nextState.dropdownOffset = window.innerHeight - offsetPageTop;
|
||||
nextState.dropdownDirection = 'bottom';
|
||||
}
|
||||
@ -178,7 +190,7 @@ class UserListItem extends Component {
|
||||
|
||||
/**
|
||||
* Check if the dropdown is visible and is opened by the user
|
||||
*
|
||||
*
|
||||
* @return True if is visible and opened by the user.
|
||||
*/
|
||||
isDropdownActivedByUser() {
|
||||
@ -188,8 +200,8 @@ class UserListItem extends Component {
|
||||
|
||||
/**
|
||||
* Return true if the content fit on the screen, false otherwise.
|
||||
*
|
||||
* @param {number} contentOffSetTop
|
||||
*
|
||||
* @param {number} contentOffSetTop
|
||||
* @param {number} contentOffsetHeight
|
||||
* @return True if the content fit on the screen, false otherwise.
|
||||
*/
|
||||
@ -230,15 +242,81 @@ class UserListItem extends Component {
|
||||
userItemContentsStyle[styles.userItemContentsCompact] = compact;
|
||||
userItemContentsStyle[styles.active] = this.state.isActionsOpen;
|
||||
|
||||
const {
|
||||
user,
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
let you = (user.isCurrent) ? intl.formatMessage(messages.you) : null;
|
||||
|
||||
let presenter = (user.isPresenter)
|
||||
? intl.formatMessage(messages.presenter)
|
||||
: null;
|
||||
|
||||
let userAriaLabel = (user.emoji.status === 'none')
|
||||
? intl.formatMessage(messages.userItemAriaLabel,
|
||||
{ username: user.name, presenter: presenter, you: you, })
|
||||
: intl.formatMessage(messages.userItemStatusAriaLabel,
|
||||
{ username: user.name,
|
||||
presenter: presenter,
|
||||
you: you,
|
||||
status: user.emoji.status, });
|
||||
|
||||
let actions = this.getAvailableActions();
|
||||
let contents = (
|
||||
<div
|
||||
className={cx(styles.userListItem, userItemContentsStyle)}
|
||||
aria-label={userAriaLabel}>
|
||||
<div className={styles.userItemContents} aria-hidden="true">
|
||||
<UserAvatar user={user} />
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!actions.length) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
const { dropdownOffset, dropdownDirection, dropdownVisible, } = this.state;
|
||||
|
||||
return (
|
||||
<li
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-live="assertive"
|
||||
aria-relevant="additions"
|
||||
className={cx(styles.userListItem, userItemContentsStyle)}>
|
||||
{this.renderUserContents()}
|
||||
</li>
|
||||
<Dropdown
|
||||
ref="dropdown"
|
||||
isOpen={this.state.isActionsOpen}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={styles.dropdown}
|
||||
autoFocus={false}
|
||||
hasPopup="true"
|
||||
ariaLive="assertive"
|
||||
ariaRelevant="additions">
|
||||
<DropdownTrigger>
|
||||
{contents}
|
||||
</DropdownTrigger>
|
||||
<DropdownContent
|
||||
style={{
|
||||
visibility: dropdownVisible ? 'visible' : 'hidden',
|
||||
[dropdownDirection]: `${dropdownOffset}px`,
|
||||
}}
|
||||
className={styles.dropdownContent}
|
||||
placement={`right ${dropdownDirection}`}>
|
||||
|
||||
<DropdownList>
|
||||
{
|
||||
[
|
||||
(<DropdownListTitle
|
||||
description={intl.formatMessage(messages.menuTitleContext)}
|
||||
key={_.uniqueId('dropdown-list-title')}>
|
||||
{user.name}
|
||||
</DropdownListTitle>),
|
||||
(<DropdownListSeparator key={_.uniqueId('action-separator')} />),
|
||||
].concat(actions)
|
||||
}
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@ -250,7 +328,7 @@ class UserListItem extends Component {
|
||||
|
||||
let actions = this.getAvailableActions();
|
||||
let contents = (
|
||||
<div tabIndex={0} className={styles.userItemContents}>
|
||||
<div className={styles.userItemContents}>
|
||||
<UserAvatar user={user} />
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
@ -269,7 +347,8 @@ class UserListItem extends Component {
|
||||
isOpen={this.state.isActionsOpen}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={styles.dropdown}>
|
||||
className={styles.dropdown}
|
||||
autoFocus={false}>
|
||||
<DropdownTrigger>
|
||||
{contents}
|
||||
</DropdownTrigger>
|
||||
@ -283,12 +362,12 @@ class UserListItem extends Component {
|
||||
|
||||
<DropdownList>
|
||||
{
|
||||
[
|
||||
(<DropdownListTitle
|
||||
description={intl.formatMessage(messages.menuTitleContext)}
|
||||
key={_.uniqueId('dropdown-list-title')}>
|
||||
{user.name}
|
||||
</DropdownListTitle>),
|
||||
[
|
||||
(<DropdownListTitle
|
||||
description={intl.formatMessage(messages.menuTitleContext)}
|
||||
key={_.uniqueId('dropdown-list-title')}>
|
||||
{user.name}
|
||||
</DropdownListTitle>),
|
||||
(<DropdownListSeparator key={_.uniqueId('action-separator')} />),
|
||||
].concat(actions)
|
||||
}
|
||||
@ -303,6 +382,7 @@ class UserListItem extends Component {
|
||||
user,
|
||||
intl,
|
||||
compact,
|
||||
meeting,
|
||||
} = this.props;
|
||||
|
||||
if (compact) {
|
||||
@ -310,6 +390,7 @@ class UserListItem extends Component {
|
||||
}
|
||||
|
||||
let userNameSub = [];
|
||||
|
||||
if (user.isPresenter) {
|
||||
userNameSub.push(intl.formatMessage(messages.presenter));
|
||||
}
|
||||
@ -320,6 +401,8 @@ class UserListItem extends Component {
|
||||
|
||||
userNameSub = userNameSub.join(' ');
|
||||
|
||||
const { disablePrivateChat, disableCam, disableMic, lockedLayout, disablePublicChat } = meeting.roomLockSettings;
|
||||
|
||||
return (
|
||||
<div className={styles.userName}>
|
||||
<span className={styles.userNameMain}>
|
||||
@ -327,6 +410,15 @@ class UserListItem extends Component {
|
||||
</span>
|
||||
<span className={styles.userNameSub}>
|
||||
{userNameSub}
|
||||
{(user.isLocked && (disablePrivateChat
|
||||
|| disableCam
|
||||
|| disableMic
|
||||
|| lockedLayout
|
||||
|| disablePublicChat)) ?
|
||||
<span> {(user.isCurrent ? ' | ' : null)}
|
||||
<Icon iconName='lock' />
|
||||
{intl.formatMessage(messages.locked)}
|
||||
</span> : null}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -394,6 +486,7 @@ class UserListItem extends Component {
|
||||
label={action.label}
|
||||
defaultMessage={action.label}
|
||||
onClick={action.handler.bind(this, ...parameters)}
|
||||
placeInTabOrder={true}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -38,6 +38,7 @@ export default class TextDrawComponent extends React.Component {
|
||||
fontStretch: 'normal',
|
||||
lineHeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'normal',
|
||||
textAlign: 'left',
|
||||
|
@ -4,7 +4,7 @@ import { Tracker } from 'meteor/tracker';
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
|
||||
import Users from '/imports/api/users';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import { makeCall, logClient } from '/imports/ui/services/api';
|
||||
|
||||
const CONNECTION_TIMEOUT = Meteor.settings.public.app.connectionTimeout;
|
||||
|
||||
@ -89,10 +89,22 @@ class Auth {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
makeCall('userLogout').then(() => {
|
||||
this.fetchLogoutUrl()
|
||||
const credentialsSnapshot = {
|
||||
meetingId: this.meetingID,
|
||||
requesterUserId: this.userID,
|
||||
requesterToken: this.token,
|
||||
};
|
||||
|
||||
// make sure users who did not connect are not added to the meeting
|
||||
// do **not** use the custom call - it relies on expired data
|
||||
Meteor.call('userLogout', credentialsSnapshot, (error, result) => {
|
||||
if (error) {
|
||||
logClient('error', { error, method: 'userLogout', credentialsSnapshot });
|
||||
} else {
|
||||
this.fetchLogoutUrl()
|
||||
.then(this.clearCredentials)
|
||||
.then(resolve);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -109,6 +121,13 @@ class Auth {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Tracker.autorun((c) => {
|
||||
if (!(credentials.meetingId && credentials.requesterToken && credentials.requesterUserId)) {
|
||||
return reject({
|
||||
error: 500,
|
||||
description: 'Authentication subscription failed due to missing credentials.',
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
c.stop();
|
||||
reject({
|
||||
|
4
bigbluebutton-html5/imports/utils/keyCodes.js
Normal file → Executable file
4
bigbluebutton-html5/imports/utils/keyCodes.js
Normal file → Executable file
@ -4,6 +4,8 @@ export const TAB = 9;
|
||||
export const ESCAPE = 27;
|
||||
export const ARROW_UP = 38;
|
||||
export const ARROW_DOWN = 40;
|
||||
export const ARROW_RIGHT = 39;
|
||||
export const ARROW_LEFT = 37;
|
||||
|
||||
export default {
|
||||
SPACE,
|
||||
@ -12,4 +14,6 @@ export default {
|
||||
ESCAPE,
|
||||
ARROW_UP,
|
||||
ARROW_DOWN,
|
||||
ARROW_RIGHT,
|
||||
ARROW_LEFT,
|
||||
};
|
||||
|
@ -2,4 +2,4 @@
|
||||
app:
|
||||
# Flag for HTTPS.
|
||||
httpsConnection: true
|
||||
connectionTimeout: 5000
|
||||
connectionTimeout: 10000
|
||||
|
14
bigbluebutton-html5/private/locales/bg_BG.json
Normal file → Executable file
14
bigbluebutton-html5/private/locales/bg_BG.json
Normal file → Executable file
@ -1,16 +1,16 @@
|
||||
{
|
||||
"app.home.greeting": "Добре дошли, {name}! Вашата презентация ще започне всеки момент",
|
||||
"app.home.greeting": "Добре дошли, {0}! Вашата презентация ще започне всеки момент",
|
||||
"app.userlist.usersTitle": "Потребители",
|
||||
"app.userlist.participantsTitle": "Участници",
|
||||
"app.userlist.messagesTitle": "Съобщения",
|
||||
"app.userlist.presenter": "Лектор",
|
||||
"app.userlist.you": "Вие",
|
||||
"app.chat.submitLabel": "Изпрати",
|
||||
"app.chat.inputLabel": "Въведи съобщение за {name}",
|
||||
"app.chat.inputPlaceholder": "Съобщение {name}",
|
||||
"app.chat.inputLabel": "Въведи съобщение за {0}",
|
||||
"app.chat.inputPlaceholder": "Съобщение {0}",
|
||||
"app.chat.titlePublic": "Общ чат",
|
||||
"app.chat.titlePrivate": "Private Chat with {name}",
|
||||
"app.chat.partnerDisconnected": "{name} has left the meeting",
|
||||
"app.chat.titlePrivate": "Private Chat with {0}",
|
||||
"app.chat.partnerDisconnected": "{0} has left the meeting",
|
||||
"app.chat.moreMessages": "More messages below",
|
||||
"app.kickMessage": "You have been kicked out of the meeting",
|
||||
"app.whiteboard.slideControls.prevSlideLabel": "Previous slide",
|
||||
@ -27,7 +27,7 @@
|
||||
"app.whiteboard.slideControls.zoomDescrip": "Change the zoom level of the presentation",
|
||||
"app.failedMessage": "Apologies, trouble connecting to the server.",
|
||||
"app.connectingMessage": "Connecting...",
|
||||
"app.waitingMessage": "Disconnected. Trying to reconnect in {seconds} seconds...",
|
||||
"app.waitingMessage": "Disconnected. Trying to reconnect in {0} seconds...",
|
||||
"app.navBar.settingsDropdown.optionsLabel": "Options",
|
||||
"app.navBar.settingsDropdown.fullscreenLabel": "Make fullscreen",
|
||||
"app.navBar.settingsDropdown.settingsLabel": "Open settings",
|
||||
@ -89,7 +89,7 @@
|
||||
"app.breakoutJoinConfirmation.confirmDesc": "Join you to the Breakout Room",
|
||||
"app.breakoutJoinConfirmation.dismissLabel": "Cancel",
|
||||
"app.breakoutJoinConfirmation.dismissDesc": "Closes and rejects Joining the Breakout Room",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {time}",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {0}",
|
||||
"app.breakoutWillCloseMessage": "Time ended. Breakout Room will close soon",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculating remaining time...",
|
||||
"app.audioModal.microphoneLabel": "Microphone",
|
||||
|
22
bigbluebutton-html5/private/locales/de.json
Normal file → Executable file
22
bigbluebutton-html5/private/locales/de.json
Normal file → Executable file
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app.home.greeting": "Willkommen {name}! Ihre Präsentation startet in Kürze...",
|
||||
"app.home.greeting": "Willkommen {0}! Ihre Präsentation startet in Kürze...",
|
||||
"app.userlist.usersTitle": "Teilnehmer",
|
||||
"app.userlist.participantsTitle": "Teilnehmer",
|
||||
"app.userlist.messagesTitle": "Nachrichten",
|
||||
@ -7,17 +7,17 @@
|
||||
"app.userlist.you": "Sie",
|
||||
"app.userlist.Label": "Teilnehmerliste",
|
||||
"app.chat.submitLabel": "Nachricht senden",
|
||||
"app.chat.inputLabel": "Chat-Nachricht eingeben für {name}",
|
||||
"app.chat.inputPlaceholder": "Nachricht an {name}",
|
||||
"app.chat.inputLabel": "Chat-Nachricht eingeben für {0}",
|
||||
"app.chat.inputPlaceholder": "Nachricht an {0}",
|
||||
"app.chat.titlePublic": "Öffentlicher Chat",
|
||||
"app.chat.titlePrivate": "Privater Chat mit {name}",
|
||||
"app.chat.partnerDisconnected": "{name} hat die Konferenz verlassen",
|
||||
"app.chat.closeChatLabel": "Schließe {title}",
|
||||
"app.chat.hideChatLabel": "Verstecke {title}",
|
||||
"app.chat.titlePrivate": "Privater Chat mit {0}",
|
||||
"app.chat.partnerDisconnected": "{0} hat die Konferenz verlassen",
|
||||
"app.chat.closeChatLabel": "Schließe {0}",
|
||||
"app.chat.hideChatLabel": "Verstecke {0}",
|
||||
"app.chat.moreMessages": "Weitere Nachrichten",
|
||||
"app.userlist.menuTitleContext": "verfügbare Optionen",
|
||||
"app.userlist.chatlistitem.unreadSingular": "{count} neue Nachricht",
|
||||
"app.userlist.chatlistitem.unreadPlural": "{count} neue Nachrichten",
|
||||
"app.userlist.chatlistitem.unreadSingular": "{0} neue Nachricht",
|
||||
"app.userlist.chatlistitem.unreadPlural": "{0} neue Nachrichten",
|
||||
"app.chat.Label": "Chat",
|
||||
"app.chat.emptyLogLabel": "Chat-Log ist leer",
|
||||
"app.media.Label": "Medien",
|
||||
@ -100,7 +100,7 @@
|
||||
"app.submenu.closedCaptions.fontColorLabel": "Schriftfarbe",
|
||||
"app.submenu.participants.muteAllLabel": "Alle stummschalten außer Präsentator",
|
||||
"app.submenu.participants.lockAllLabel": "Alle Teilnehmer sperren",
|
||||
"app.submenu.participants.lockItemLabel": "Teilnehmer {lockItem}",
|
||||
"app.submenu.participants.lockItemLabel": "Teilnehmer {0}",
|
||||
"app.submenu.participants.lockMicDesc": "Deaktiviert das Mikrofon für alle gesperrten Teilnehmer",
|
||||
"app.submenu.participants.lockCamDesc": "Deaktiviert die Webcam für alle gesperrten Teilnehmer",
|
||||
"app.submenu.participants.lockPublicChatDesc": "Deaktiviert den öffentlichen Chat für alle gesperrten Teilnehmer",
|
||||
@ -158,7 +158,7 @@
|
||||
"app.breakoutJoinConfirmation.confirmDesc": "Dem Breakout-Raum beitreten",
|
||||
"app.breakoutJoinConfirmation.dismissLabel": "Abbrechen",
|
||||
"app.breakoutJoinConfirmation.dismissDesc": "Beitritt zum Breakout-Raum ablehnen",
|
||||
"app.breakoutTimeRemainingMessage": "Verbleibende Zeit für den Breakout-Raum: {time}",
|
||||
"app.breakoutTimeRemainingMessage": "Verbleibende Zeit für den Breakout-Raum: {0}",
|
||||
"app.breakoutWillCloseMessage": "Zeit abgelaufen. Der Breakout-Raum wird in Kürze geschlossen",
|
||||
"app.calculatingBreakoutTimeRemaining": "Berechne die verbleibende Zeit...",
|
||||
"app.audioModal.microphoneLabel": "Mit Mikrofon",
|
||||
|
@ -1,29 +1,32 @@
|
||||
{
|
||||
"app.home.greeting": "Welcome {name}! Your presentation will begin shortly...",
|
||||
"app.home.greeting": "Welcome {0}! Your presentation will begin shortly...",
|
||||
"app.userlist.usersTitle": "Users",
|
||||
"app.userlist.participantsTitle": "Participants",
|
||||
"app.userlist.messagesTitle": "Messages",
|
||||
"app.userlist.presenter": "Presenter",
|
||||
"app.userlist.you": "You",
|
||||
"app.userlist.locked": "Locked",
|
||||
"app.userlist.Label": "User List",
|
||||
"app.chat.submitLabel": "Send Message",
|
||||
"app.chat.inputLabel": "Message input for chat {name}",
|
||||
"app.chat.inputPlaceholder": "Message {name}",
|
||||
"app.chat.inputLabel": "Message input for chat {0}",
|
||||
"app.chat.inputPlaceholder": "Message {0}",
|
||||
"app.chat.titlePublic": "Public Chat",
|
||||
"app.chat.titlePrivate": "Private Chat with {name}",
|
||||
"app.chat.partnerDisconnected": "{name} has left the meeting",
|
||||
"app.chat.closeChatLabel": "Close {title}",
|
||||
"app.chat.hideChatLabel": "Hide {title}",
|
||||
"app.chat.titlePrivate": "Private Chat with {0}",
|
||||
"app.chat.partnerDisconnected": "{0} has left the meeting",
|
||||
"app.chat.closeChatLabel": "Close {0}",
|
||||
"app.chat.hideChatLabel": "Hide {0}",
|
||||
"app.chat.moreMessages": "More messages below",
|
||||
"app.userlist.menuTitleContext": "available options",
|
||||
"app.userlist.chatlistitem.unreadSingular": "{count} New Message",
|
||||
"app.userlist.chatlistitem.unreadPlural": "{count} New Messages",
|
||||
"app.userlist.chatlistitem.unreadSingular": "{0} New Message",
|
||||
"app.userlist.chatlistitem.unreadPlural": "{0} New Messages",
|
||||
"app.userlist.menu.chat.label": "Chat",
|
||||
"app.userlist.menu.clearStatus.label": "Clear Status",
|
||||
"app.userlist.menu.makePresenter.label": "Make Presenter",
|
||||
"app.userlist.menu.kickUser.label": "Kick user",
|
||||
"app.userlist.menu.muteUserAudio.label": "Mute user",
|
||||
"app.userlist.menu.unmuteUserAudio.label": "Unmute user",
|
||||
"app.userlist.useritem.nostatus.arialabel": "{username} {presenter} {you}",
|
||||
"app.userlist.useritem.status.arialabel": "{username} {presenter} {you} current status {status}",
|
||||
"app.chat.Label": "Chat",
|
||||
"app.chat.emptyLogLabel": "Chat log empty",
|
||||
"app.media.Label": "Media",
|
||||
@ -42,7 +45,7 @@
|
||||
"app.polling.pollingTitle": "Polling Options",
|
||||
"app.failedMessage": "Apologies, trouble connecting to the server.",
|
||||
"app.connectingMessage": "Connecting...",
|
||||
"app.waitingMessage": "Disconnected. Trying to reconnect in {seconds} seconds...",
|
||||
"app.waitingMessage": "Disconnected. Trying to reconnect in {0} seconds...",
|
||||
"app.navBar.settingsDropdown.optionsLabel": "Options",
|
||||
"app.navBar.settingsDropdown.fullscreenLabel": "Make fullscreen",
|
||||
"app.navBar.settingsDropdown.settingsLabel": "Open settings",
|
||||
@ -107,7 +110,7 @@
|
||||
"app.submenu.closedCaptions.fontColorLabel": "Font color",
|
||||
"app.submenu.participants.muteAllLabel": "Mute all except the presenter",
|
||||
"app.submenu.participants.lockAllLabel": "Lock all participants",
|
||||
"app.submenu.participants.lockItemLabel": "Participants {lockItem}",
|
||||
"app.submenu.participants.lockItemLabel": "Participants {0}",
|
||||
"app.submenu.participants.lockMicDesc": "Disables the microphone for all locked participants",
|
||||
"app.submenu.participants.lockCamDesc": "Disables the webcam for all locked participants",
|
||||
"app.submenu.participants.lockPublicChatDesc": "Disables public chat for all locked participants",
|
||||
@ -161,7 +164,7 @@
|
||||
"app.actionsBar.emojiMenu.thumbsupDesc": "Change your status to thumbs up",
|
||||
"app.actionsBar.emojiMenu.thumbsdownLabel": "Thumbs down",
|
||||
"app.actionsBar.emojiMenu.thumbsdownDesc": "Change your status to thumbs down",
|
||||
"app.actionsBar.currentStatusDesc": "current status {status}",
|
||||
"app.actionsBar.currentStatusDesc": "current status {0}",
|
||||
"app.audioNotification.audioFailedMessage": "Your audio connection failed to connect",
|
||||
"app.audioNotification.mediaFailedMessage": "getUserMicMedia failed, Only secure origins are allowed",
|
||||
"app.audioNotification.closeLabel": "Close",
|
||||
@ -171,7 +174,7 @@
|
||||
"app.breakoutJoinConfirmation.confirmDesc": "Join you to the Breakout Room",
|
||||
"app.breakoutJoinConfirmation.dismissLabel": "Cancel",
|
||||
"app.breakoutJoinConfirmation.dismissDesc": "Closes and rejects Joining the Breakout Room",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {time}",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {0}",
|
||||
"app.breakoutWillCloseMessage": "Time ended. Breakout Room will close soon",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculating remaining time...",
|
||||
"app.audioModal.microphoneLabel": "Microphone",
|
||||
|
14
bigbluebutton-html5/private/locales/pt_BR.json
Normal file → Executable file
14
bigbluebutton-html5/private/locales/pt_BR.json
Normal file → Executable file
@ -1,16 +1,16 @@
|
||||
{
|
||||
"app.home.greeting": "Bem-vindo {name}! Sua aprensentação começará em breve...",
|
||||
"app.home.greeting": "Bem-vindo {0}! Sua aprensentação começará em breve...",
|
||||
"app.userlist.usersTitle": "Users",
|
||||
"app.userlist.participantsTitle": "Participantes",
|
||||
"app.userlist.messagesTitle": "Mensagens",
|
||||
"app.userlist.presenter": "Apresentador",
|
||||
"app.userlist.you": "Você",
|
||||
"app.chat.submitLabel": "Enviar Mensagem",
|
||||
"app.chat.inputLabel": "Campo de mensagem para conversa {name}",
|
||||
"app.chat.inputPlaceholder": "Message {name}",
|
||||
"app.chat.inputLabel": "Campo de mensagem para conversa {0}",
|
||||
"app.chat.inputPlaceholder": "Message {0}",
|
||||
"app.chat.titlePublic": "Conversa Publíca",
|
||||
"app.chat.titlePrivate": "Conversa Privada com {name}",
|
||||
"app.chat.partnerDisconnected": "{name} saiu da sala",
|
||||
"app.chat.titlePrivate": "Conversa Privada com {0}",
|
||||
"app.chat.partnerDisconnected": "{0} saiu da sala",
|
||||
"app.chat.moreMessages": "Mais mensagens abaixo",
|
||||
"app.kickMessage": "Você foi expulso da apresentação",
|
||||
"app.whiteboard.slideControls.prevSlideLabel": "Slide Anterior",
|
||||
@ -27,7 +27,7 @@
|
||||
"app.whiteboard.slideControls.zoomDescrip": "Change the zoom level of the presentation",
|
||||
"app.failedMessage": "Desculpas, estamos com problemas para se conectar ao servidor.",
|
||||
"app.connectingMessage": "Conectando...",
|
||||
"app.waitingMessage": "Desconectado. Tentando reconectar em {seconds} segundos...",
|
||||
"app.waitingMessage": "Desconectado. Tentando reconectar em {0} segundos...",
|
||||
"app.navBar.settingsDropdown.optionsLabel": "Options",
|
||||
"app.navBar.settingsDropdown.fullscreenLabel": "Make fullscreen",
|
||||
"app.navBar.settingsDropdown.settingsLabel": "Open settings",
|
||||
@ -89,7 +89,7 @@
|
||||
"app.breakoutJoinConfirmation.confirmDesc": "Join you to the Breakout Room",
|
||||
"app.breakoutJoinConfirmation.dismissLabel": "Cancel",
|
||||
"app.breakoutJoinConfirmation.dismissDesc": "Closes and rejects Joining the Breakout Room",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {time}",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout Room time remaining: {0}",
|
||||
"app.breakoutWillCloseMessage": "Time ended. Breakout Room will close soon",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculating remaining time...",
|
||||
"app.audioModal.microphoneLabel": "Microphone",
|
||||
|
@ -1,8 +1,8 @@
|
||||
<recordID>${r.getId()}</recordID>
|
||||
|
||||
<#if r.getMeeting()??>
|
||||
<meetingID>${r.getMeeting().getId()?html}</meetingID>
|
||||
<externalMeetingID>${r.getMeeting().getExternalId()?html}</externalMeetingID>
|
||||
<meetingID>${r.getMeeting().getExternalId()?html}</meetingID>
|
||||
<internalMeetingID>${r.getMeeting().getId()?html}</internalMeetingID>
|
||||
<name>${r.getMeeting().getName()?html}</name>
|
||||
<isBreakout>${r.getMeeting().isBreakout()?c}</isBreakout>
|
||||
<#else>
|
||||
|
@ -72,6 +72,7 @@ def process_archived_meetings(recording_dir)
|
||||
if step_succeeded
|
||||
BigBlueButton.logger.info("Process format #{process_type} succeeded for #{meeting_id}")
|
||||
BigBlueButton.logger.info("Process took #{step_time}ms")
|
||||
IO.write("#{recording_dir}/process/#{process_type}/#{meeting_id}/processing_time", step_time)
|
||||
else
|
||||
BigBlueButton.logger.info("Process format #{process_type} failed for #{meeting_id}")
|
||||
BigBlueButton.logger.info("Process took #{step_time}ms")
|
||||
|
Loading…
Reference in New Issue
Block a user