diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala index d3c3523499f1ce3a915d10bb4450cdf0c5f4c278..e4fac4ee47cce79b91ecdeda420fc8f99d64d4cc 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala @@ -67,6 +67,10 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent route[CheckAlivePongSysMsg](envelope, jsonNode) case UserEmojiChangedEvtMsg.NAME => route[UserEmojiChangedEvtMsg](envelope, jsonNode) + case PresenterUnassignedEvtMsg.NAME => + route[PresenterUnassignedEvtMsg](envelope, jsonNode) + case PresenterAssignedEvtMsg.NAME => + route[PresenterAssignedEvtMsg](envelope, jsonNode) case UserJoinedMeetingEvtMsg.NAME => route[UserJoinedMeetingEvtMsg](envelope, jsonNode) case UserLeftMeetingEvtMsg.NAME => diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala index 69083fb43661231062c42d6416bd89b3dec616b9..0c8148fe1620a63d02eeae2e8d00274c8e5284b7 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala @@ -26,6 +26,8 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW) case m: MeetingDestroyedEvtMsg => handleMeetingDestroyedEvtMsg(m) case m: CheckAlivePongSysMsg => handleCheckAlivePongSysMsg(m) case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m) + case m: PresenterUnassignedEvtMsg => handlePresenterUnassignedEvtMsg(m) + case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(m) case m: UserJoinedMeetingEvtMsg => handleUserJoinedMeetingEvtMsg(m) case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m) case m: UserJoinedVoiceConfToClientEvtMsg => handleUserJoinedVoiceConfToClientEvtMsg(m) @@ -76,6 +78,14 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW) } + def handlePresenterUnassignedEvtMsg(msg: PresenterUnassignedEvtMsg): Unit = { + olgMsgGW.handle(new UserStatusChanged(msg.header.meetingId, msg.body.intId, "presenter", "false")) + } + + def handlePresenterAssignedEvtMsg(msg: PresenterAssignedEvtMsg): Unit = { + olgMsgGW.handle(new UserStatusChanged(msg.header.meetingId, msg.body.presenterId, "presenter", "true")) + } + def handleUserEmojiChangedEvtMsg(msg: UserEmojiChangedEvtMsg): Unit = { //listener.handle(new UserStatusChanged(meetingId, userid, status, value)) } diff --git a/bigbluebutton-client/branding/default/style/css/V2Theme.css b/bigbluebutton-client/branding/default/style/css/V2Theme.css index 72420e9da87306c377e789183ec1e266ad3f196b..2336294da2ee38df1d6c791cd1159383e4bbdabe 100755 --- a/bigbluebutton-client/branding/default/style/css/V2Theme.css +++ b/bigbluebutton-client/branding/default/style/css/V2Theme.css @@ -250,6 +250,16 @@ phonecomponents|MuteMeButton { iconColorDown : #FFFFFF; } +.mainActionButton, .cameraDisplaySettingsWindowStartBtn { + borderThickness : 0; + color : #FFFFFF; + fillColorUp : #1070D7; + fillColorOver : #0A5EAC; + fillColorDown : #1070D7; + textRollOverColor : #FFFFFF; + textSelectedColor : #FFFFFF; +} + .voiceConfDefaultButtonStyle { icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Audio"); disabledIcon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Audio_Disabled"); @@ -1227,6 +1237,12 @@ mx|Panel { color : #2A2D33; } +.pollVoteLabel { + textAlign : center; + fontSize : 16; + fontWeight : bold; +} + .presentationFilesList { paddingBottom : 5; paddingLeft : 5; @@ -1289,6 +1305,19 @@ poll|PollResultsModal { fontSize : 14; } +.pollHintBoxStyle { + horizontalAlign : center; + verticalAlign : middle; + backgroundColor : #CDD4DB; + paddingLeft : 10; + paddingRight : 10; +} + +.pollHintTextStyle { + textAlign : center; + fontWeight : bold; +} + /* //------------------------------ // ProgressBar @@ -1678,7 +1707,7 @@ videoconf|UserGraphicHolder { } .cameraDisplaySettingsWindowStartBtn { - icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Webcam"); + icon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Webcam_On"); disabledIcon : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_Webcam_Disabled"); } diff --git a/bigbluebutton-client/build.xml b/bigbluebutton-client/build.xml index 0f3c3e15495dd06837046c690a570f915c06eb15..49afe82cf197a7a0e7d9255be4b33bade178157e 100755 --- a/bigbluebutton-client/build.xml +++ b/bigbluebutton-client/build.xml @@ -126,6 +126,10 @@ </sequential> </target> + <target name="locale"> + <compileLocale locale="${LOCALE}" /> + </target> + <target name="localize"> <compileLocale locale="${LOCALE}" /> </target> diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties index e85a5449b1807d6811371b859a67ecbe74d24bbb..798b33cd7963d34ba27bffd7ef468c4240505352 100755 --- a/bigbluebutton-client/locale/en_US/bbbResources.properties +++ b/bigbluebutton-client/locale/en_US/bbbResources.properties @@ -246,6 +246,7 @@ bbb.presentation.fitToWidth.toolTip = Fit Presentation To Width bbb.presentation.fitToPage.toolTip = Fit Presentation To Page bbb.presentation.uploadPresBtn.toolTip = Upload Presentation bbb.presentation.downloadPresBtn.toolTip = Download Presentations +bbb.presentation.poll.response = Response to poll bbb.presentation.backBtn.toolTip = Previous slide bbb.presentation.btnSlideNum.accessibilityName = Slide {0} of {1} bbb.presentation.btnSlideNum.toolTip = Select a slide @@ -755,6 +756,7 @@ bbb.polling.publishButton.label = Publish bbb.polling.closeButton.label = Close bbb.polling.customPollOption.label = Custom Poll... bbb.polling.pollModal.title = Live Poll Results +bbb.polling.pollModal.hint = Leave this window open to allow students to respond to the poll. Selecting the Publish or Close button will end the poll. bbb.polling.customChoices.title = Enter Polling Choices bbb.polling.respondersLabel.novotes = Waiting for responses bbb.polling.respondersLabel.text = {0} Users Responded @@ -815,6 +817,7 @@ bbb.lockSettings.lockOnJoin=Lock On Join bbb.users.breakout.breakoutRooms = Breakout Rooms bbb.users.breakout.updateBreakoutRooms = Update Breakout Rooms +bbb.users.breakout.timerForRoom.toolTip = Time left for this breakout room bbb.users.breakout.timer.toolTip = Time left for breakout rooms bbb.users.breakout.calculatingRemainingTime = Calculating remaining time... bbb.users.breakout.closing = Closing diff --git a/bigbluebutton-client/locale/zh_TW/bbbResources.properties b/bigbluebutton-client/locale/zh_TW/bbbResources.properties index 24b91e29afe53b83fc19b667d65ebf4696a4f64d..1db7f5d3e27d087bddaf2f25c729b1067e58a58d 100644 --- a/bigbluebutton-client/locale/zh_TW/bbbResources.properties +++ b/bigbluebutton-client/locale/zh_TW/bbbResources.properties @@ -645,7 +645,7 @@ bbb.shortcuthelp.dropdown.users = 用戶相關快æ·éµ bbb.shortcuthelp.dropdown.caption = Closed Caption shortcuts bbb.shortcuthelp.browserWarning.text = The full list of shortcuts are only supported in Internet Explorer. bbb.shortcuthelp.headers.shortcut = å¿«æ·éµ -bbb.shortcuthelp.headers.function = 功能\ +bbb.shortcuthelp.headers.function = 功能 bbb.shortcutkey.general.minimize = 189 bbb.shortcutkey.general.minimize.function = 最å°åŒ–ç•¶å‰è¦–窗 diff --git a/bigbluebutton-client/resources/config.xml.template b/bigbluebutton-client/resources/config.xml.template index a0881070970d58d3671b0f6781a82fabacad7b2a..80dfc6256446995c4b788d7079ecafbbe94bd9f0 100755 --- a/bigbluebutton-client/resources/config.xml.template +++ b/bigbluebutton-client/resources/config.xml.template @@ -40,6 +40,7 @@ enableEmojiStatus="true" enableSettingsButton="true" enableGuestUI="false" + moderatorUnmute="true" baseTabIndex="301" /> diff --git a/bigbluebutton-client/resources/prod/BigBlueButton.html b/bigbluebutton-client/resources/prod/BigBlueButton.html index bc6a6c37ee7f7ebcd84f37dd211d4b29e93fa1a6..15a8c952d1c11d41806e92b88b59900173be631d 100755 --- a/bigbluebutton-client/resources/prod/BigBlueButton.html +++ b/bigbluebutton-client/resources/prod/BigBlueButton.html @@ -164,7 +164,7 @@ </script> <script type="text/javascript"> window.onload = function() { - var checkRequest = $.ajax({ + let checkRequest = $.ajax({ dataType: 'json', url: '/html5client/check' }); @@ -178,35 +178,8 @@ }; function html5() { - // no Flash detected on the client - var originalPath, enterRequest, authToken, meetingId, userId; - originalPath = document.location.pathname; - - // use the enter api to detect the meetingid, userid and authToken - // and reuse them to join via the HTML5 client - enterRequest = $.ajax({ - dataType: 'json', - url: '/bigbluebutton/api/enter' + document.location.search - }); - - enterRequest.done(function(enterData) { - meetingId = enterData.response.meetingID; - userId = enterData.response.externUserID; - authToken = enterData.response.authToken; - - if ((meetingId != null) && (userId != null) && (authToken != null)) { - // redirect to the html5 client with the received info - // format <IP>/html5client/<meetingId>/<userId>/<authToken> - document.location.pathname = "/html5client/join/"+meetingId+"/"+userId+"/"+authToken; - } else { - // go back to the redirection page - document.location.pathname = originalPath; - } - }); - - enterRequest.fail(function(enterData, textStatus, errorThrown){ - BBBLog.debug("Enter request failed"); - }); + // Navigate to HTML5 login + document.location.pathname = "/html5client/join"; } </script> </head> diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/model/users/VoiceUsers2x.as b/bigbluebutton-client/src/org/bigbluebutton/core/model/users/VoiceUsers2x.as index 514a75d6e1bd8520ac182c57311a457b3c8b268d..a16e6241eedd95f452036b72fc28d27f3105985d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/core/model/users/VoiceUsers2x.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/model/users/VoiceUsers2x.as @@ -7,8 +7,14 @@ package org.bigbluebutton.core.model.users private var _users:ArrayCollection = new ArrayCollection(); - public function add(user: VoiceUser2x):void { - _users.addItem(user); + public function add(nuser: VoiceUser2x):void { + var index:int = getIndex(nuser.intId); + if (index != -1) { + // replace this user with the new user + _users.setItemAt(nuser, index); + } else { + _users.addItem(nuser); + } } public function remove(userId: String):VoiceUser2x { @@ -59,7 +65,7 @@ package org.bigbluebutton.core.model.users return -1; } - + public function getVoiceOnlyUsers():Array { var temp: Array = new Array(); for (var i:int = 0; i < _users.length; i++) { diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/options/HelpOptions.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/options/HelpOptions.as old mode 100644 new mode 100755 index 9bb905e61bbbf2adcc10e021de0b2b15d9fbb959..fd76e5b72b8d0474e647aedea3051a4c00f3ae0f --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/options/HelpOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/options/HelpOptions.as @@ -22,7 +22,7 @@ package org.bigbluebutton.main.model.options { public class HelpOptions extends Options { [Bindable] - public var url:String = ""; + public var url:String = "http://www.bigbluebutton.org/content/videos"; public function HelpOptions() { name = "help"; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/BBBSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/BBBSettings.mxml index 3fb67b2d8389383e7263e09c8c38d5ac5ed9bef2..ef9fc85597cad67749505ce61f05ce6a43d2827e 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/BBBSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/BBBSettings.mxml @@ -54,7 +54,7 @@ <mx:VBox id="addedComponents" paddingTop="12" height="100%" /> <mx:ControlBar width="100%" horizontalAlign="right"> - <mx:Button id="okBtn" label="{ResourceUtil.getInstance().getString('bbb.settings.ok')}" click="onOkClicked()"/> + <mx:Button id="okBtn" styleName="mainActionButton" label="{ResourceUtil.getInstance().getString('bbb.settings.ok')}" click="onOkClicked()"/> <mx:Button id="cancelBtn" label="{ResourceUtil.getInstance().getString('bbb.settings.cancel')}" click="onCancelClicked()"/> </mx:ControlBar> </mx:TitleWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/FlashMicSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/FlashMicSettings.mxml old mode 100644 new mode 100755 index 33699919a028ad68c4be3a19ece89d47b8b75099..f9b180daa6514a71a2a836310ac903babb6ab473 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/FlashMicSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/FlashMicSettings.mxml @@ -290,13 +290,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. var currentSlider:Slider=Slider(event.currentTarget); mic.gain = currentSlider.value } - - private var DEFAULT_HELP_URL:String = "http://www.bigbluebutton.org/content/videos"; private function onHelpButtonClicked():void { var helpOptions : HelpOptions = Options.getOptions(HelpOptions) as HelpOptions; - DEFAULT_HELP_URL = helpOptions.url; - navigateToURL(new URLRequest(DEFAULT_HELP_URL)); + navigateToURL(new URLRequest(helpOptions.url)); } private function yesButtonClicked():void { @@ -388,6 +385,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. styleName="micSettingsWindowSpeakIntoMicLabelStyle" /> <mx:Button id="yesButton" label="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestAudioYes')}" click="yesButtonClicked()" + styleName="mainActionButton" toolTip=""/> <mx:Button id="noButton" label="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestAudioNo')}" click="noButtonClicked()" @@ -438,6 +436,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:HBox width="100%" horizontalAlign="right" horizontalGap="18" paddingTop="10"> <mx:Button id="echoTestButton" label="{ResourceUtil.getInstance().getString('bbb.micSettings.nextButton')}" click="echoTestButtonClickHandler()" + styleName="mainActionButton" toolTip="{ResourceUtil.getInstance().getString('bbb.micSettings.nextButton.toolTip')}"/> <mx:Button id="playButton" label="{ResourceUtil.getInstance().getString('bbb.micSettings.playSound')}" click="playButtonClickHandler()" toggle="true" diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml index 5c71250e4f27fe0e113785e2d0ebc43fac9864ac..cba5732e212519d275465df4cc0a217e15491978 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/LockSettings.mxml @@ -141,7 +141,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </mx:HBox> <mx:HBox width="100%" horizontalAlign="right" horizontalGap="18" paddingTop="20"> - <mx:Button id="saveBtn" label="{ResourceUtil.getInstance().getString('bbb.lockSettings.save')}" + <mx:Button id="saveBtn" label="{ResourceUtil.getInstance().getString('bbb.lockSettings.save')}" + styleName="mainActionButton" click="onSaveClicked()" toolTip="{ResourceUtil.getInstance().getString('bbb.lockSettings.save.tooltip')}"/> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml index 0fbf395e636970c051ac7b421b832631dd28983b..82d3ae20890b63350c07da17290c672329793505 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml @@ -132,6 +132,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </fx:Script> <mx:VBox width="100%" height="100%" horizontalAlign="center"> <mx:Text text="{message}" textAlign="center"/> - <mx:Button id="okBtn" label="{ResourceUtil.getInstance().getString('bbb.logout.button.label')}" click="callSignOut()"/> + <mx:Button id="okBtn" styleName="mainActionButton" label="{ResourceUtil.getInstance().getString('bbb.logout.button.label')}" click="callSignOut()"/> </mx:VBox> </mx:TitleWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml index 2383fdee806a0c1d7a766a442c7bd4c667097c9c..1937c3316856163391d0784e88882a375bc59c64 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml @@ -808,7 +808,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function updateToolbarHeight():void { if (toolbarHeight != 0) { - toolbarHeight = Math.max(DEFAULT_TOOLBAR_HEIGHT, toolbar.logo.height + toolbar.quickLinks.includeInLayout ? toolbar.quickLinks.height : 0 + 10); + toolbarHeight = Math.max(DEFAULT_TOOLBAR_HEIGHT, toolbar.logo.height + toolbar.quickLinks.includeInLayout ? toolbar.quickLinks.height : 0); if (UsersUtil.isBreakout()) { toolbarHeight += toolbar.breakoutRibbon.height; } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml index f0ecb8e80fd0425245f1186973b0ba2baeb9559c..f939574e9649e32d20de1b5da79741da5641a9ca 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml @@ -89,6 +89,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.main.events.SuccessfulLoginEvent; import org.bigbluebutton.main.model.NetworkStatsData; import org.bigbluebutton.main.model.options.BrandingOptions; + import org.bigbluebutton.main.model.options.HelpOptions; import org.bigbluebutton.main.model.options.LayoutOptions; import org.bigbluebutton.main.model.options.ShortcutKeysOptions; import org.bigbluebutton.main.model.users.events.ChangeMyRole; @@ -98,8 +99,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.util.i18n.ResourceUtil; private static const LOGGER:ILogger = getClassLogger(MainToolbar); - - private var DEFAULT_HELP_URL:String = "http://www.bigbluebutton.org/content/videos"; public static const ALIGN_RIGHT:String ="ALIGN_RIGHT"; public static const ALIGN_LEFT:String = "ALIGN_LEFT"; @@ -289,7 +288,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. } private function onHelpButtonClicked():void { - navigateToURL(new URLRequest(DEFAULT_HELP_URL)) + var helpOptions:HelpOptions = Options.getOptions(HelpOptions) as HelpOptions; + navigateToURL(new URLRequest(helpOptions.url)); } private function handleEndMeetingEvent(event:BBBEvent):void { @@ -542,7 +542,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:Label text="|" visible="{timeRemaining.visible}" includeInLayout="{timeRemaining.visible}"/> <mx:Label id="timeRemaining" visible="false" includeInLayout="{timeRemaining.visible}" text="{ResourceUtil.getInstance().getString('bbb.users.breakout.calculatingRemainingTime')}" - toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timer.toolTip')}"/> + toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timerForRoom.toolTip')}"/> </mx:HBox> <!-- Top bar --> <mx:HBox id="topBox" width="100%" verticalAlign="middle" horizontalScrollPolicy="off" styleName="topBoxStyle"> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/OldLocaleWarnWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/OldLocaleWarnWindow.mxml index 7bbaebd0912ffce272a27b1890b457dcd7a8960a..dad78b7929ad187b032b837c9864763d9c63a353 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/OldLocaleWarnWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/OldLocaleWarnWindow.mxml @@ -1,86 +1,86 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - -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. - -You should have received a copy of the GNU Lesser General Public License along -with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. - ---> - -<mx:TitleWindow xmlns:mx="library://ns.adobe.com/flex/mx" - xmlns:fx="http://ns.adobe.com/mxml/2009" - title="{windowTitle}" showCloseButton="false" creationComplete="init()" - x="168" y="86" layout="vertical" width="400" height="150" horizontalAlign="center"> - <fx:Script> - <![CDATA[ - import org.as3commons.logging.api.ILogger; - import org.as3commons.logging.api.getClassLogger; - import org.bigbluebutton.core.BBB; - import org.bigbluebutton.core.PopUpUtil; - import org.bigbluebutton.util.i18n.ResourceUtil; - - private static const LOGGER:ILogger = getClassLogger(OldLocaleWarnWindow); - private const windowTitleDefault:String = "Warning: Old Language Version"; - private const reminder1Default:String = "You have an old language translation of BigBlueButton."; - private const reminder2Default:String = "Please clear your browser cache and try again."; - - [Bindable] private var windowTitle:String; - [Bindable] private var oldLocalesReminder1:String; - [Bindable] private var oldLocalesReminder2:String; - - private function init():void { - addEventListener(Event.CLOSE, onUserLoggedOutWindowClose); - - var locWindowTitle:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.windowTitle'); - if ((locWindowTitle == null) || (locWindowTitle == "")) windowTitle = windowTitleDefault; - else windowTitle = locWindowTitle; - - var reminder1:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.reminder1'); - if ((reminder1 == null) || (reminder1 == "")) oldLocalesReminder1 = reminder1Default; - else oldLocalesReminder1 = reminder1; - - var reminder2:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.reminder2'); - if ((reminder2 == null) || (reminder2 == "")) oldLocalesReminder2 = reminder2Default; - else oldLocalesReminder2 = reminder2; - } - - private function redirect():void { - var logoutURL:String = BBB.getLogoutURL(); - var request:URLRequest = new URLRequest(logoutURL); - LOGGER.debug("Log out url: " + logoutURL); - request.method = URLRequestMethod.GET; - var urlLoader:URLLoader = new URLLoader(); - urlLoader.addEventListener(Event.COMPLETE, handleComplete); - urlLoader.load(request); - } - - private function handleComplete(e:Event):void { - var request:URLRequest = new URLRequest(BBB.getLogoutURL()); - navigateToURL(request, '_self'); - PopUpUtil.removePopUp(this); - } - - private function onUserLoggedOutWindowClose(e:Event):void { - PopUpUtil.removePopUp(this); - } - - ]]> - </fx:Script> - <mx:Label text="{oldLocalesReminder1}"/> - <mx:Label text="{oldLocalesReminder2}"/> - <mx:Button id="okBtn" label="OK" click="redirect()"/> - +<?xml version="1.0" encoding="utf-8"?> + +<!-- + +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. + +You should have received a copy of the GNU Lesser General Public License along +with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + +--> + +<mx:TitleWindow xmlns:mx="library://ns.adobe.com/flex/mx" + xmlns:fx="http://ns.adobe.com/mxml/2009" + title="{windowTitle}" showCloseButton="false" creationComplete="init()" + x="168" y="86" layout="vertical" width="400" height="150" horizontalAlign="center"> + <fx:Script> + <![CDATA[ + import org.as3commons.logging.api.ILogger; + import org.as3commons.logging.api.getClassLogger; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.PopUpUtil; + import org.bigbluebutton.util.i18n.ResourceUtil; + + private static const LOGGER:ILogger = getClassLogger(OldLocaleWarnWindow); + private const windowTitleDefault:String = "Warning: Old Language Version"; + private const reminder1Default:String = "You have an old language translation of BigBlueButton."; + private const reminder2Default:String = "Please clear your browser cache and try again."; + + [Bindable] private var windowTitle:String; + [Bindable] private var oldLocalesReminder1:String; + [Bindable] private var oldLocalesReminder2:String; + + private function init():void { + addEventListener(Event.CLOSE, onUserLoggedOutWindowClose); + + var locWindowTitle:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.windowTitle'); + if ((locWindowTitle == null) || (locWindowTitle == "")) windowTitle = windowTitleDefault; + else windowTitle = locWindowTitle; + + var reminder1:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.reminder1'); + if ((reminder1 == null) || (reminder1 == "")) oldLocalesReminder1 = reminder1Default; + else oldLocalesReminder1 = reminder1; + + var reminder2:String = ResourceUtil.getInstance().getString('bbb.oldlocalewindow.reminder2'); + if ((reminder2 == null) || (reminder2 == "")) oldLocalesReminder2 = reminder2Default; + else oldLocalesReminder2 = reminder2; + } + + private function redirect():void { + var logoutURL:String = BBB.getLogoutURL(); + var request:URLRequest = new URLRequest(logoutURL); + LOGGER.debug("Log out url: " + logoutURL); + request.method = URLRequestMethod.GET; + var urlLoader:URLLoader = new URLLoader(); + urlLoader.addEventListener(Event.COMPLETE, handleComplete); + urlLoader.load(request); + } + + private function handleComplete(e:Event):void { + var request:URLRequest = new URLRequest(BBB.getLogoutURL()); + navigateToURL(request, '_self'); + PopUpUtil.removePopUp(this); + } + + private function onUserLoggedOutWindowClose(e:Event):void { + PopUpUtil.removePopUp(this); + } + + ]]> + </fx:Script> + <mx:Label text="{oldLocalesReminder1}"/> + <mx:Label text="{oldLocalesReminder2}"/> + <mx:Button id="okBtn" styleName="mainActionButton" label="OK" click="redirect()"/> + </mx:TitleWindow> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/WebRTCEchoTest.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/WebRTCEchoTest.mxml index 84c08ffce026db3fc35a669ee1d3963a77cae4f5..87f69ff8a22bba353570f058119c8a8bbf408a83 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/WebRTCEchoTest.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/WebRTCEchoTest.mxml @@ -58,7 +58,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.util.i18n.ResourceUtil; private static const LOGGER:ILogger = getClassLogger(WebRTCEchoTest); - private static var DEFAULT_HELP_URL:String = "http://www.bigbluebutton.org/content/videos"; private static const TIMEOUT:Number = 60; private static const CANCEL_BUTTON:Number = 55; @@ -243,8 +242,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function onHelpButtonClicked():void { var helpOptions : HelpOptions = Options.getOptions(HelpOptions) as HelpOptions; - DEFAULT_HELP_URL = helpOptions.url; - navigateToURL(new URLRequest(DEFAULT_HELP_URL)); + navigateToURL(new URLRequest(helpOptions.url)); } @@ -308,11 +306,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. text="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestMicPrompt')}" styleName="micSettingsWindowSpeakIntoMicLabelStyle" /> <mx:Button id="yesButton" label="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestAudioYes')}" - click="yesButtonClicked()" + click="yesButtonClicked()" + styleName="mainActionButton" toolTip=""/> - <mx:Button id="noButton" - label="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestAudioNo')}" - click="noButtonClicked()" + <mx:Button id="noButton" + label="{ResourceUtil.getInstance().getString('bbb.micSettings.echoTestAudioNo')}" + click="noButtonClicked()" toolTip=""/> </mx:HBox> </mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollChoicesModal.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollChoicesModal.mxml index 78ad3fc08cd94dfa7d3c15cc0a1d139bde308cdd..816fc93020d6a6336ae25863ee02e9d24d29510e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollChoicesModal.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollChoicesModal.mxml @@ -89,6 +89,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:HBox width="100%" horizontalGap="10" horizontalAlign="right"> <mx:Button id="publishButton" click="publishButton_clickHandler(event)" + styleName="mainActionButton" label="{ResourceUtil.getInstance().getString('bbb.polling.startButton.label')}"/> <mx:Button id="closeButton" click="onCloseClicked()" label="{ResourceUtil.getInstance().getString('bbb.polling.closeButton.label')}"/> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollResultsModal.as b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollResultsModal.as index f5af7d9c0ee2e6d9d89205c4ee23540338be7d1b..d17267aa69c3efc3bdf44da5bb5f4e8c4e0fd49e 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollResultsModal.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/PollResultsModal.as @@ -7,6 +7,7 @@ package org.bigbluebutton.modules.polling.views import flash.events.TimerEvent; import flash.utils.Timer; + import mx.containers.Box; import mx.containers.HBox; import mx.containers.TitleWindow; import mx.controls.Button; @@ -50,6 +51,17 @@ package org.bigbluebutton.modules.polling.views modalTitle.maxWidth = 300; addChild(modalTitle); + var hintBox : Box = new Box(); + hintBox.percentWidth = 100; + hintBox.styleName = "pollHintBoxStyle"; + addChild(hintBox); + + var hintText : AdvancedLabel = new AdvancedLabel(); + hintText.percentWidth = 100; + hintText.styleName = "pollHintTextStyle"; + hintText.text = ResourceUtil.getInstance().getString('bbb.polling.pollModal.hint'); + hintBox.addChild(hintText); + var hrule:HRule = new HRule(); hrule.percentWidth = 100; addChild(hrule); @@ -89,6 +101,7 @@ package org.bigbluebutton.modules.polling.views _publishBtn = new Button(); _publishBtn.label = ResourceUtil.getInstance().getString('bbb.polling.publishButton.label'); + _publishBtn.styleName = "mainActionButton"; _publishBtn.addEventListener(MouseEvent.CLICK, handlePublishClick); botBox.addChild(_publishBtn); _closeBtn = new Button(); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml index e698f608a40391017484fbb6a5b9ec917bd142c7..808fa663963574b3dac9ff3b39320cbf93024dd5 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml @@ -70,6 +70,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import mx.collections.ArrayCollection; import mx.controls.Menu; import mx.events.MenuEvent; + import mx.managers.PopUpManager; import flashx.textLayout.formats.Direction; @@ -694,6 +695,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. if (pollResultsPopUp) { pollResultsPopUp.setPoll(e.poll); } + PopUpManager.centerPopUp(pollResultsPopUp); } else { //switch to vote state setControlBarState("vote"); @@ -871,6 +873,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToPage.toolTip')}" click="onFitToPage(true)"/> </mx:HBox> - <mx:HBox id="pollVoteBox" width="100%" height="100%" visible="false" includeInLayout="false" horizontalAlign="center" verticalAlign="middle"/> + <mx:VBox width="100%" height="100%" visible="{pollVoteBox.visible}" includeInLayout="{pollVoteBox.includeInLayout}" horizontalAlign="center"> + <mx:Label id="pollVoteLabel" styleName="pollVoteLabel" text="{ResourceUtil.getInstance().getString('bbb.presentation.poll.response')}"/> + <mx:HBox id="pollVoteBox" width="100%" height="100%" visible="false" includeInLayout="false" horizontalAlign="center" verticalAlign="middle"/> + </mx:VBox> </mx:ControlBar> </pres:CustomMdiWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/screenshare/view/components/ScreensharePublishWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/screenshare/view/components/ScreensharePublishWindow.mxml index cc627779d0576a9e2c0c0e4dd19069304e64a743..52461fc6ae8fd5b5b4b945fe08193bf2f9e4ebe9 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/screenshare/view/components/ScreensharePublishWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/screenshare/view/components/ScreensharePublishWindow.mxml @@ -619,8 +619,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:ComboBox id="shareTypeCombo" dataProvider="{shareTypeProvider}" /> </mx:HBox> <mx:Spacer width="80%" /> - <mx:Button id="startBtn" click="onStartButtonClick()" label="{ResourceUtil.getInstance().getString('bbb.screensharePublish.startButton.label')}" /> + <mx:Button id="startBtn" styleName="mainActionButton" click="onStartButtonClick()" label="{ResourceUtil.getInstance().getString('bbb.screensharePublish.startButton.label')}" /> <mx:Button id="cancelBtn" click="closeWindow()" label="{ResourceUtil.getInstance().getString('bbb.screensharePublish.cancelButton.label')}" /> - <mx:Button id="stopBtn" visible="false" includeInLayout="false" click="close()" label="{ResourceUtil.getInstance().getString('bbb.screensharePublish.stopButton.label')}" /> + <mx:Button id="stopBtn" styleName="mainActionButton" visible="false" includeInLayout="false" click="close()" label="{ResourceUtil.getInstance().getString('bbb.screensharePublish.stopButton.label')}" /> </mx:ControlBar> </common:CustomMdiWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/UsersOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/UsersOptions.as index cdfa54a4459669f95c145425107249e0a697a158..1cb05e10695062781c94dd642fd569c7b59bd2df 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/UsersOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/model/UsersOptions.as @@ -33,7 +33,10 @@ package org.bigbluebutton.modules.users.model { [Bindable] public var enableSettingsButton:Boolean = true; - + + [Bindable] + public var moderatorUnmute:Boolean = true; + [Bindable] public var enableGuestUI:Boolean = false; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/BreakoutRoomSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/BreakoutRoomSettings.mxml index 32ec37d7f1faa723a510e9eb7ed23da849c4e736..0062d520004bf22948b8c1aee5c5377920bab3a4 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/BreakoutRoomSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/BreakoutRoomSettings.mxml @@ -314,6 +314,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:HBox width="100%" horizontalAlign="right" verticalGap="15"> <mx:Button id="startButton" + styleName="mainActionButton" click="this.mode == 'create' ? createBreakoutRooms() : inviteUsersToBreakoutRooms()"/> <mx:Button label="{ResourceUtil.getInstance().getString('bbb.users.breakout.close')}" click="onCloseClicked()"/> </mx:HBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/MediaItemRenderer.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/MediaItemRenderer.mxml index 51fb279705019af138123d80d55634dc3aa8b447..f5ca881c0e039e0eaab078fa62a504cfda2d36a6 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/MediaItemRenderer.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/MediaItemRenderer.mxml @@ -68,22 +68,22 @@ private var options:UsersOptions; private var myMenu:Menu = null; - - private function onCreationComplete():void{ - refreshRole(UsersUtil.amIModerator()); - - this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler); - - BindingUtils.bindSetter(updateButtons, listenOnlyInd, "visible"); - BindingUtils.bindSetter(updateButtons, voiceJoinedInd, "visible"); - BindingUtils.bindSetter(updateButtons, muteInd, "visible"); - BindingUtils.bindSetter(updateButtons, userLockedInd, "visible"); - BindingUtils.bindSetter(updateButtons, hasStreamInd, "visible"); - BindingUtils.bindSetter(updateButtons, viewingStreamInd, "visible"); - - options = Options.getOptions(UsersOptions) as UsersOptions; - } - + + private function onCreationComplete():void { + options = Options.getOptions(UsersOptions) as UsersOptions; + + refreshRole(UsersUtil.amIModerator()); + + this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler); + + BindingUtils.bindSetter(updateButtons, listenOnlyInd, "visible"); + BindingUtils.bindSetter(updateButtons, voiceJoinedInd, "visible"); + BindingUtils.bindSetter(updateButtons, muteInd, "visible"); + BindingUtils.bindSetter(updateButtons, userLockedInd, "visible"); + BindingUtils.bindSetter(updateButtons, hasStreamInd, "visible"); + BindingUtils.bindSetter(updateButtons, viewingStreamInd, "visible"); + } + private function dataChangeHandler(e:Event):void { //rest rolledOver when the data changes because onRollOut wont be called if the row moves if (data != null) { @@ -171,29 +171,29 @@ e.internalUserID = data.userId; dispatchEvent(e); } - + private function updateButtons(voiceMuted:Boolean = false):void { // reset the mute image filter so the talking indicator doesn't stick muteImg.filters = null; - + var ls:LockSettingsVO = UsersUtil.getLockSettings(); - + if (data != null) { settingsBtn.visible = rolledOver && !data.me && !UsersUtil.isBreakout(); - - if (!data.inVoiceConf) { - muteImg.visible = false; - muteImg.includeInLayout = false; - muteBtn.visible = false; - muteBtn.includeInLayout = true; + + if ( !data.inVoiceConf || ( options.moderatorUnmute == false && UsersUtil.amIModerator() && !UsersUtil.isMe(data.userId) ) ) { + muteImg.visible = false; + muteImg.includeInLayout = false; + muteBtn.visible = false; + muteBtn.includeInLayout = true; } else { - if (data.listenOnly) { - muteImg.source = getStyle("iconSound"); - muteImg.visible = true; - muteImg.includeInLayout = true; - muteBtn.visible = false; - muteBtn.includeInLayout = false; - } else if (data.locked && ls.getDisableMic()) { + if (data.listenOnly) { + muteImg.source = getStyle("iconSound"); + muteImg.visible = true; + muteImg.includeInLayout = true; + muteBtn.visible = false; + muteBtn.includeInLayout = false; + } else if (data.locked && ls.getDisableMic()) { muteImg.visible = true; muteImg.includeInLayout = true; muteBtn.visible = false; @@ -205,20 +205,20 @@ muteBtn.includeInLayout = rolledOver; muteBtn.enabled = true; - if(data.talking && !rolledOver){ + if (data.talking && !rolledOver) { muteImg.filters = [new GlowFilter(getStyle("glowFilterColor"), 1, 6, 6, 2, BitmapFilterQuality.HIGH, false, false)]; - }else{ + } else { muteImg.filters = []; } } } - - if (data.role == Role.MODERATOR){ + + if (data.role == Role.MODERATOR) { lockImg.visible = false; lockImg.includeInLayout = true; lockBtn.visible = false; lockBtn.includeInLayout = false; - } else if(moderator && ls.isAnythingLocked()) { + } else if (moderator && ls.isAnythingLocked()) { lockImg.visible = !rolledOver; lockImg.includeInLayout = !rolledOver; lockBtn.visible = rolledOver; @@ -230,7 +230,7 @@ lockBtn.visible = false; lockBtn.includeInLayout = false; } - + if (data.hasStream) { // if it's myself or if I'm watching all the streams from the given user, then don't activate the button if (data.me || data.isViewingAllStreams()) { @@ -253,20 +253,19 @@ if (!rolledOver) { if (data.inVoiceConf) { - if (data.listenOnly) { - muteImg.source = getStyle("iconSound"); - } else if (data.muted) { - muteImg.source = getStyle("iconAudioMuted"); - } else { - muteImg.source = getStyle("iconAudio"); - } + if (data.listenOnly) { + muteImg.source = getStyle("iconSound"); + } else if (data.muted) { + muteImg.source = getStyle("iconAudioMuted"); + } else { + muteImg.source = getStyle("iconAudio"); + } } - - - if ( data.locked && !data.presenter && ls.isAnythingLocked() ) { + + + if (data.locked && !data.presenter && ls.isAnythingLocked()) { lockImg.source = getStyle("iconLock"); - } - else { + } else { lockImg.source = null; } } else { @@ -274,7 +273,7 @@ muteBtn.setStyle("icon", getStyle("iconAudio")); else muteBtn.setStyle("icon", getStyle("iconAudioMuted")); - + if (data.locked == rolledOverLock) lockBtn.setStyle("icon", getStyle("iconUnlock")); else diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml index c1aa784e4c28438b8ba7573ff12afa22ccac9a63..83a32b116251dad95afa4f504f6629a5c806f956 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml @@ -384,13 +384,15 @@ $Id: $ paramsMenuData = []; paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.clearAllStatus'), icon: getStyle('iconClearStatus'), handler: resetEmojiStatuses}); - if (!roomMuted) { - paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.muteAll'), icon: getStyle('iconAudioMuted'), handler: muteAll}); - var presenter:User2x = UsersUtil.getPresenter(); - if (presenter != null) - paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.muteAllExcept') + ": " + presenter.name, icon: getStyle('iconAudioMuted'), handler: muteAlmostAll}); - } else - paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.unmuteAll'), icon: getStyle('iconAudio'), handler: muteAll}); + if (partOptions.moderatorUnmute == true) { + if (!roomMuted) { + paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.muteAll'), icon: getStyle('iconAudioMuted'), handler: muteAll}); + var presenter:User2x = UsersUtil.getPresenter(); + if (presenter != null) + paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.muteAllExcept') + ": " + presenter.name, icon: getStyle('iconAudioMuted'), handler: muteAlmostAll}); + } else + paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.unmuteAll'), icon: getStyle('iconAudio'), handler: muteAll}); + } paramsMenuData.push({label: ResourceUtil.getInstance().getString('bbb.users.settings.lockSettings'), icon: getStyle('iconLock'), handler: lockSettings}); if (breakoutOptions.enabled && amIModerator && !UsersUtil.isBreakout()) { diff --git a/bigbluebutton-html5/client/stylesheets/bbb-icons.css b/bigbluebutton-html5/client/stylesheets/bbb-icons.css index 5e8b131cc437e9096b4663bd255d3adaecd0f435..efda7c393348ed712996ee5a1b21ec0d2bc671a2 100755 --- a/bigbluebutton-html5/client/stylesheets/bbb-icons.css +++ b/bigbluebutton-html5/client/stylesheets/bbb-icons.css @@ -1,32 +1,32 @@ @font-face { - font-family: 'bbb-icons'; - src: url('/fonts/BbbIcons/bbb-icons.eot?j1ntjp'); - src: url('/fonts/BbbIcons/bbb-icons.eot?j1ntjp#iefix') format('embedded-opentype'), - url('/fonts/BbbIcons/bbb-icons.ttf?j1ntjp') format('truetype'), - url('/fonts/BbbIcons/bbb-icons.woff?j1ntjp') format('woff'), - url('/fonts/BbbIcons/bbb-icons.svg?j1ntjp#bbb-icons') format('svg'); - font-weight: normal; - font-style: normal; + font-family: 'bbb-icons'; + src: url('/fonts/BbbIcons/bbb-icons.eot?j1ntjp'); + src: url('/fonts/BbbIcons/bbb-icons.eot?j1ntjp#iefix') format('embedded-opentype'), + url('/fonts/BbbIcons/bbb-icons.ttf?j1ntjp') format('truetype'), + url('/fonts/BbbIcons/bbb-icons.woff?j1ntjp') format('woff'), + url('/fonts/BbbIcons/bbb-icons.svg?j1ntjp#bbb-icons') format('svg'); + font-weight: normal; + font-style: normal; } [class^="icon-bbb-"], [class*=" icon-bbb-"] { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'bbb-icons' !important; - speak: none; - position: relative; - /*top: 1px;*/ - display: inline-block; - font-style: normal; - font-weight: 400; - line-height: 1; - -webkit-font-smoothing: antialiased; - width: 1.28571429em; - text-align: center; - vertical-align: middle; + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'bbb-icons' !important; + speak: none; + position: relative; + /*top: 1px;*/ + display: inline-block; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + width: 1.28571429em; + text-align: center; + vertical-align: middle; - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .icon-bbb-mute:before { @@ -45,7 +45,7 @@ content: "\e92a"; } .icon-bbb-exit_fullscreen:before { - content: "\e935"; + content: "\e935"; } .icon-bbb-settings:before { content: "\e92b"; @@ -95,8 +95,13 @@ .icon-bbb-video:before { content: "\e930"; } +.icon-bbb-elipsis:before { + content: "\e902"; +} .icon-bbb-more:before { content: "\e902"; + display: inline-block; + transform: rotate(90deg); } .icon-bbb-promote:before { content: "\e903"; diff --git a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js index 2a56cdfc19aa40d3b4695245a89d89cd4df73f91..0a6fb16c2778324220857a4e8c56ea6c1b3efd8d 100644 --- a/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js +++ b/bigbluebutton-html5/imports/api/2.0/annotations/server/methods/sendAnnotation.js @@ -6,7 +6,9 @@ import { check } from 'meteor/check'; import Annotations from '/imports/api/2.0/annotations'; function isLastMessage(annotation, userId) { - if (annotation.status === 'DRAW_END') { + const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end; + + if (annotation.status === DRAW_END) { const selector = { id: annotation.id, userId, diff --git a/bigbluebutton-html5/imports/api/2.0/audio/client/manager/index.js b/bigbluebutton-html5/imports/api/2.0/audio/client/manager/index.js index 81e9d19ea6ed5d01793291918005fe92e91973ed..621f720136696728c28e9ec1fa5308dc1bf3d67d 100644 --- a/bigbluebutton-html5/imports/api/2.0/audio/client/manager/index.js +++ b/bigbluebutton-html5/imports/api/2.0/audio/client/manager/index.js @@ -213,7 +213,11 @@ class AudioManager { } resolve({ stunServers: stunServers.map(server => server.url), - turnServers: turnServers.map(server => server.url), + turnServers: turnServers.map(server => ({ + urls: server.url, + username: server.username, + password: server.password, + })), }); }); return promise; diff --git a/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeLockSettings.js b/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeLockSettings.js index 49fb1b5bc911ef896d37918c859fd680885c13da..dcbdf772546a01ca6dc85f66a519a2c9560f0e5f 100644 --- a/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeLockSettings.js +++ b/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeLockSettings.js @@ -30,9 +30,11 @@ export default function changeLockSettings(meetingId, payload) { return Logger.error(`Changing meeting={${meetingId}} lock settings: ${err}`); } - if (numChanged) { - return Logger.error(`Changed meeting={${meetingId}} updated lock settings`); + if (!numChanged) { + return Logger.info(`meeting={${meetingId}} lock settings were not updated`); } + + return Logger.info(`Changed meeting={${meetingId}} updated lock settings`); }; return Meetings.upsert(selector, modifier, cb); diff --git a/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeUserLock.js b/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeUserLock.js index ebb0647db9b8dea6e3cfb27b3d308806af194db1..f9cd16a5ab0d57b7d31ae6367b3114a78771591d 100644 --- a/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeUserLock.js +++ b/bigbluebutton-html5/imports/api/2.0/meetings/server/modifiers/changeUserLock.js @@ -10,20 +10,28 @@ export default function changeUserLock(meetingId, payload) { lockedBy: String, }); + const { userId, locked, lockedBy } = payload; + const selector = { - userId: payload.userId, + userId, }; const modifier = { $set: { - locked: payload.locked, + locked, }, }; - const cb = (err) => { + const cb = (err, numChanged) => { if (err) { return Logger.error(`Changing user lock setting: ${err}`); } + + if (!numChanged) { + return Logger.info(`User's userId=${userId} lock status wasn't updated`); + } + + return Logger.info(`User's userId=${userId} lock status was changed to: ${locked} by user userId=${lockedBy}`); }; return Users.upsert(selector, modifier, cb); diff --git a/bigbluebutton-html5/imports/api/2.0/presentations/server/handlers/presentationConversionUpdate.js b/bigbluebutton-html5/imports/api/2.0/presentations/server/handlers/presentationConversionUpdate.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/api/2.0/presentations/server/methods/removePresentation.js b/bigbluebutton-html5/imports/api/2.0/presentations/server/methods/removePresentation.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/api/2.0/presentations/server/methods/setPresentation.js b/bigbluebutton-html5/imports/api/2.0/presentations/server/methods/setPresentation.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/addSlide.js b/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/addSlide.js index be19f2a06e76a0b1601d8e93aa5fcf38804dcce6..e28f6e79aa5ff5334fdabeddd6b4c6bb494089c1 100644 --- a/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/addSlide.js +++ b/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/addSlide.js @@ -93,6 +93,14 @@ export default function addSlide(meetingId, presentationId, slide) { return fetchImageSizes(imageUri) .then(({ width, height }) => { + // there is a rare case when for a very long not-active meeting + // the presentation files just disappear + // in that case just set the whole calculatedData to undefined + if (!width && !height) { + modifier.$set.calculatedData = undefined; + return Slides.upsert(selector, modifier, cb); + } + // pre-calculating the width, height, and vieBox coordinates / dimensions // to unload the client-side const slideData = { diff --git a/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/resizeSlide.js b/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/resizeSlide.js index 0ad0eca27f2424db207f557bb1726e8c1d370ad0..ad00b543067d6d309509c2c9379f111aa443a72f 100644 --- a/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/resizeSlide.js +++ b/bigbluebutton-html5/imports/api/2.0/slides/server/modifiers/resizeSlide.js @@ -27,19 +27,19 @@ export default function resizeSlide(meetingId, slide) { // fetching the current slide data // and pre-calculating the width, height, and vieBox coordinates / sizes // to reduce the client-side load - const _slide = Slides.findOne(selector); + const Slide = Slides.findOne(selector); const slideData = { - width: _slide.calculatedData.width, - height: _slide.calculatedData.height, + width: Slide.calculatedData.width, + height: Slide.calculatedData.height, xOffset, yOffset, widthRatio, heightRatio, }; const calculatedData = calculateSlideData(slideData); - calculatedData.imageUri = _slide.calculatedData.imageUri; - calculatedData.width = _slide.calculatedData.width; - calculatedData.height = _slide.calculatedData.height; + calculatedData.imageUri = Slide.calculatedData.imageUri; + calculatedData.width = Slide.calculatedData.width; + calculatedData.height = Slide.calculatedData.height; modifier.$set.calculatedData = calculatedData; const cb = (err, numChanged) => { diff --git a/bigbluebutton-html5/imports/api/2.0/users/server/eventHandlers.js b/bigbluebutton-html5/imports/api/2.0/users/server/eventHandlers.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/api/2.0/users/server/handlers/changeRole.js b/bigbluebutton-html5/imports/api/2.0/users/server/handlers/changeRole.js old mode 100755 new mode 100644 index 711e25bbeb6dbf553ed020e8ec8bf3f70dedf93e..5b0638cd2f0d763973a8ea6038a1acaf8d5803b7 --- a/bigbluebutton-html5/imports/api/2.0/users/server/handlers/changeRole.js +++ b/bigbluebutton-html5/imports/api/2.0/users/server/handlers/changeRole.js @@ -1,36 +1,9 @@ -import Logger from '/imports/startup/server/logger'; import { check } from 'meteor/check'; -import Users from '/imports/api/2.0/users'; +import changeRole from '../modifiers/changeRole'; -export default function handleChangeRole({body}, meetingId) { - - const { userId, role, changedBy } = body; - - check(userId, String); - check(role, String); - check(changedBy, String); - - const selector = { - meetingId, - userId, - }; - - const modifier = { - $set: { - role, - }, - }; - - const cb = (err, numChanged) => { - if (err) { - return Logger.error(`Changed user role: ${err}`); - } - - if (numChanged) { - return Logger.info(`Changed user role ${role} id=${userId} meeting=${meetingId} by changedBy=${changedBy}`); - } - }; - - return Users.update(selector, modifier, cb); +export default function handleChangeRole(payload, meetingId) { + check(payload.body, Object); + check(meetingId, String); + changeRole(payload, meetingId); } diff --git a/bigbluebutton-html5/imports/api/2.0/users/server/methods.js b/bigbluebutton-html5/imports/api/2.0/users/server/methods.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/api/2.0/users/server/methods/changeRole.js b/bigbluebutton-html5/imports/api/2.0/users/server/methods/changeRole.js old mode 100755 new mode 100644 index 74591ce9dc3cf556e0faf2520068f40e37aacb7c..5e40381a504b0c612a6ffc9fff610758dd07ec34 --- a/bigbluebutton-html5/imports/api/2.0/users/server/methods/changeRole.js +++ b/bigbluebutton-html5/imports/api/2.0/users/server/methods/changeRole.js @@ -26,12 +26,6 @@ export default function changeRole(credentials, userId, role) { 'user-not-found', `You need a valid user to be able to set '${role}'`); } - const header = { - name: EVENT_NAME, - meetingId, - userId, - }; - const payload = { userId, role, @@ -40,5 +34,5 @@ export default function changeRole(credentials, userId, role) { Logger.verbose(`User '${userId}' setted as '${role} role by '${requesterUserId}' from meeting '${meetingId}'`); - return RedisPubSub.publish(CHANNEL, EVENT_NAME, meetingId, payload, header); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/2.0/users/server/modifiers/changeRole.js b/bigbluebutton-html5/imports/api/2.0/users/server/modifiers/changeRole.js new file mode 100644 index 0000000000000000000000000000000000000000..01518275a665c5161bd8ac6e1c71d6ccf2e46943 --- /dev/null +++ b/bigbluebutton-html5/imports/api/2.0/users/server/modifiers/changeRole.js @@ -0,0 +1,34 @@ +import Logger from '/imports/startup/server/logger'; +import Users from '/imports/api/2.0/users'; + +export default function changeRole({ body }, meetingId) { + const { userId, role, changedBy } = body; + + const selector = { + meetingId, + userId, + }; + + const modifier = { + $set: { + role, + }, + $push: { + roles: (role === 'MODERATOR' ? 'moderator' : 'viewer'), + }, + }; + + const cb = (err, numChanged) => { + if (err) { + return Logger.error(`Changed user role: ${err}`); + } + + if (numChanged) { + return Logger.info(`Changed user role ${role} id=${userId} meeting=${meetingId} by changedBy=${changedBy}`); + } + + return null; + }; + + return Users.update(selector, modifier, cb); +} diff --git a/bigbluebutton-html5/imports/api/acl/Acl.js b/bigbluebutton-html5/imports/api/acl/Acl.js index 208b29c48ed0b8bd9f5279d03b6bb1c7cbb05229..f031a2a3e6c82e710e78b568933efdb89aba1b09 100644 --- a/bigbluebutton-html5/imports/api/acl/Acl.js +++ b/bigbluebutton-html5/imports/api/acl/Acl.js @@ -1,8 +1,9 @@ + +import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import deepMerge from '/imports/utils/deepMerge'; -export class Acl { - +export default class Acl { constructor(config, Users) { this.Users = Users; this.config = config; @@ -12,11 +13,22 @@ export class Acl { check(permission, String); const permissions = this.getPermissions(credentials); - if (permissions) { - return this.fetchPermission(permission, permissions); - } + return this.checkToken(credentials) && this.fetchPermission(permission, permissions); + } - return false; + checkToken(credentials) { + // skip token check in client `can` calls since we dont have the authToken in the collection + if (!Meteor.isServer) return true; + + const { meetingId, requesterUserId: userId, requesterToken: authToken } = credentials; + + const User = this.Users.findOne({ + meetingId, + userId, + authToken, + }); + + return !!User; // if he found a user means the meeting/user/token is valid } fetchPermission(permission, permissions) { diff --git a/bigbluebutton-html5/imports/startup/acl.js b/bigbluebutton-html5/imports/startup/acl.js index 693f0b22126b7d78e4409016ab405b5baef516b9..cfee53ae5ab51c613b82b458b1b95a4b8d6b6910 100644 --- a/bigbluebutton-html5/imports/startup/acl.js +++ b/bigbluebutton-html5/imports/startup/acl.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; import Users from '/imports/api/2.0/users'; -import { Acl } from '/imports/api/acl/Acl'; +import Acl from '/imports/api/acl/Acl'; const AclSingleton = new Acl(); diff --git a/bigbluebutton-html5/imports/startup/server/index.js b/bigbluebutton-html5/imports/startup/server/index.js index 869fffc72aa6dae48b345c2a4bb91fe4be1b07c4..b1aa6ff9bd87ca13e7ab4ac1787426230f265fc2 100644 --- a/bigbluebutton-html5/imports/startup/server/index.js +++ b/bigbluebutton-html5/imports/startup/server/index.js @@ -21,7 +21,7 @@ WebApp.connectHandlers.use('/check', (req, res, next) => { WebApp.connectHandlers.use('/locale', (req, res) => { const APP_CONFIG = Meteor.settings.public.app; - const defaultLocale = APP_CONFIG.defaultLocale; + const defaultLocale = APP_CONFIG.defaultSettings.application.locale; const localeRegion = req.query.locale.split('-'); let messages = {}; const locales = [defaultLocale, localeRegion[0]]; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx index ac4833c1ae8829757b1badca5c9934b8b18d7415..efd132d6fe463d2f0875d770f5ef650130a7518b 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; @@ -10,129 +10,7 @@ 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'; -const propTypes = { - // Emoji status of the current user - userEmojiStatus: PropTypes.string.isRequired, - actions: PropTypes.object.isRequired, -}; - -class EmojiMenu extends Component { - constructor(props) { - super(props); - } - - render() { - const { - userEmojiStatus, - actions, - intl, - } = this.props; - - return ( - <Dropdown autoFocus> - <DropdownTrigger tabIndex={0}> - <Button - role="button" - label={intl.formatMessage(intlMessages.statusTriggerLabel)} - aria-label={intl.formatMessage(intlMessages.changeStatusLabel)} - aria-describedby="currentStatus" - icon="hand" - ghost={false} - circle - hideLabel={false} - color="primary" - size="lg" - - // FIXME: Without onClick react proptypes keep warning - // even after the DropdownTrigger inject an onClick handler - onClick={() => null} - > - <div id="currentStatus" hidden> - { - intl.formatMessage(intlMessages.currentStatusDesc, { 0: userEmojiStatus }) - } - </div> - </Button> - </DropdownTrigger> - <DropdownContent placement="top left"> - <DropdownList> - <DropdownListItem - icon="hand" - 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 - icon="clear_status" - label={intl.formatMessage(intlMessages.clearLabel)} - description={intl.formatMessage(intlMessages.clearDesc)} - onClick={() => actions.setEmojiHandler('none')} - tabIndex={-1} - /> - </DropdownList> - </DropdownContent> - </Dropdown> - ); - } -} +import { EMOJI_NORMALIZE } from '/imports/utils/statuses'; const intlMessages = defineMessages({ statusTriggerLabel: { @@ -229,5 +107,118 @@ const intlMessages = defineMessages({ }, }); +const propTypes = { + // Emoji status of the current user + userEmojiStatus: PropTypes.string.isRequired, + actions: PropTypes.object.isRequired, +}; + +const EmojiMenu = ({ + userEmojiStatus, + actions, + intl, +}) => ( + <Dropdown autoFocus> + <DropdownTrigger tabIndex={0}> + <Button + role="button" + label={intl.formatMessage(intlMessages.statusTriggerLabel)} + aria-label={intl.formatMessage(intlMessages.changeStatusLabel)} + aria-describedby="currentStatus" + icon={userEmojiStatus in EMOJI_NORMALIZE ? + EMOJI_NORMALIZE[userEmojiStatus] : 'hand'} + ghost={false} + circle + hideLabel={false} + color="primary" + size="lg" + + // FIXME: Without onClick react proptypes keep warning + // even after the DropdownTrigger inject an onClick handler + onClick={() => null} + > + <div id="currentStatus" hidden> + { intl.formatMessage(intlMessages.currentStatusDesc, { 0: userEmojiStatus }) } + </div> + </Button> + </DropdownTrigger> + <DropdownContent placement="top left"> + <DropdownList> + <DropdownListItem + icon="hand" + 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 + icon="clear_status" + label={intl.formatMessage(intlMessages.clearLabel)} + description={intl.formatMessage(intlMessages.clearDesc)} + onClick={() => actions.setEmojiHandler('none')} + tabIndex={-1} + /> + </DropdownList> + </DropdownContent> + </Dropdown> +); + EmojiMenu.propTypes = propTypes; export default injectIntl(EmojiMenu); diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index 709d37304763f93e155a03f620ed202f1651e5cb..4813274ec076f96d6ca1b2c0f2701057c92a6c16 100644 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -2,7 +2,7 @@ import React, { cloneElement } from 'react'; import { createContainer } from 'meteor/react-meteor-data'; import { withRouter } from 'react-router'; import { defineMessages, injectIntl } from 'react-intl'; - +import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/2.0/users'; import Breakouts from '/imports/api/2.0/breakouts'; @@ -13,6 +13,7 @@ import ClosedCaptionsContainer from '/imports/ui/components/closed-captions/cont import { getFontSize, getCaptionsStatus, + meetingIsBreakout, } from './service'; import { withModalMounter } from '../modal/service'; @@ -22,6 +23,14 @@ import NavBarContainer from '../nav-bar/container'; import ActionsBarContainer from '../actions-bar/container'; import MediaContainer from '../media/container'; +const propTypes = { + navbar: PropTypes.node, + actionsbar: PropTypes.node, + media: PropTypes.node, + location: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, +}; + const defaultProps = { navbar: <NavBarContainer />, actionsbar: <ActionsBarContainer />, @@ -45,10 +54,22 @@ const intlMessages = defineMessages({ const AppContainer = (props) => { // inject location on the navbar container - const navbarWithLocation = cloneElement(props.navbar, { location: props.location }); + const { + navbar, + actionsbar, + media, + ...otherProps + } = props; + + const navbarWithLocation = cloneElement(navbar, { location: props.location }); return ( - <App {...props} navbar={navbarWithLocation}> + <App + navbar={navbarWithLocation} + actionsbar={actionsbar} + media={media} + {...otherProps} + > {props.children} </App> ); @@ -83,7 +104,9 @@ export default withRouter(injectIntl(withModalMounter(createContainer(( // forcelly logged out when the meeting is ended Meetings.find({ meetingId: Auth.meetingID }).observeChanges({ removed() { - sendToError(410, intl.formatMessage(intlMessages.endMeetingMessage)); + if (!meetingIsBreakout) { + sendToError(410, intl.formatMessage(intlMessages.endMeetingMessage)); + } }, }); @@ -101,3 +124,4 @@ export default withRouter(injectIntl(withModalMounter(createContainer(( }, AppContainer)))); AppContainer.defaultProps = defaultProps; +AppContainer.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index f25bf612ef695c5024225866bd0a243f3e05180d..c10260a0a9d692ca892889de8fa09bf2827f85e4 100644 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -46,6 +46,7 @@ $btn-jumbo-padding: $jumbo-padding-y $jumbo-padding-x; vertical-align: middle; cursor: pointer; user-select: none; + transition: all .2s ease-in-out; &:hover, &:focus { @@ -99,7 +100,6 @@ $btn-jumbo-padding: $jumbo-padding-y $jumbo-padding-x; opacity: .85; display: block; margin-top: $btn-spacing; - font-size: 90%; color: #fff; font-weight: normal; line-height: 1.5; diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx index 87c94d86fa9e83d1e17e15af1c5feb80854d7bcc..894ec742b1b63a6e38625575567840ad315f9048 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx @@ -4,7 +4,7 @@ import cx from 'classnames'; import { withModalMounter } from '/imports/ui/components/modal/service'; import Clipboard from 'clipboard'; import _ from 'lodash'; -import Button from '/imports/ui/components/button/component'; +import Icon from '/imports/ui/components/icon/component'; import Dropdown from '/imports/ui/components/dropdown/component'; import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component'; import DropdownContent from '/imports/ui/components/dropdown/content/component'; @@ -118,17 +118,12 @@ class ChatDropdown extends Component { onShow={this.onActionsShow} onHide={this.onActionsHide} > - <DropdownTrigger tabIndex={0}> - <Button - label={intl.formatMessage(intlMessages.options)} - icon="more" - circle - hideLabel - className={cx(styles.btn, styles.btnSettings)} - // FIXME: Without onClick react proptypes keep warning - // even after the DropdownTrigger inject an onClick handler - onClick={() => null} - /> + <DropdownTrigger + tabIndex={0} + ariaLabel={intl.formatMessage(intlMessages.options)} + className={styles.btn} + > + <Icon iconName="more" /> </DropdownTrigger> <DropdownContent placement="bottom right"> <DropdownList> diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss index 020412197717d4e7be0b66a512b1c7d76e6ac9ef..d45076abba5c8f1576b77289aaf34479039fb0a6 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/styles.scss @@ -2,18 +2,5 @@ .btn { flex: 0 0; - margin-left: $sm-padding-x / 2; - - i{ - color: black !important; - } - - &:hover, - &:focus { - span { - background-color: transparent !important; - color: $color-white !important; - opacity: .75; - } - } + margin-top: auto; } diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index 6bb60620934b3e95fe0df71cebe70c20547a9e74..e40eb35f60c80bc7966fee4022b418ac88fb461c 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -1,6 +1,8 @@ -import React, { Component } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import { Link } from 'react-router'; import { defineMessages, injectIntl } from 'react-intl'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import styles from './styles'; import MessageForm from './message-form/component'; import MessageList from './message-list/component'; @@ -20,86 +22,102 @@ const intlMessages = defineMessages({ }, }); -class Chat extends Component { - constructor(props) { - super(props); - } +const Chat = (props) => { + const { + chatID, + chatName, + title, + messages, + scrollPosition, + hasUnreadMessages, + lastReadMessageTime, + partnerIsLoggedOut, + isChatLocked, + minMessageLength, + maxMessageLength, + actions, + intl, + } = props; - componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - render() { - const { - chatID, - chatName, - title, - messages, - scrollPosition, - hasUnreadMessages, - lastReadMessageTime, - partnerIsLoggedOut, - isChatLocked, - minMessageLength, - maxMessageLength, - actions, - intl, - } = this.props; - - return ( - <div className={styles.chat}> - <header className={styles.header}> - <div className={styles.title}> + return ( + <div className={styles.chat}> + <header className={styles.header}> + <div className={styles.title}> + <Link + to="/users" + role="button" + aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })} + > + <Icon iconName="left_arrow" /> {title} + </Link> + </div> + { + chatID !== 'public' ? <Link to="/users" role="button" - aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })} + className={styles.closeIcon} + aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} > - <Icon iconName="left_arrow" /> {title} - </Link> - </div> - { - chatID !== 'public' ? - <Link - to="/users" - role="button" - className={styles.closeIcon} - aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })} - > - <Icon iconName="close" onClick={() => actions.handleClosePrivateChat(chatID)} /> - </Link> : - <ChatDropdown /> - } - </header> - <MessageList - chatId={chatID} - messages={messages} - id={ELEMENT_ID} - scrollPosition={scrollPosition} - hasUnreadMessages={hasUnreadMessages} - handleScrollUpdate={actions.handleScrollUpdate} - handleReadMessage={actions.handleReadMessage} - lastReadMessageTime={lastReadMessageTime} - partnerIsLoggedOut={partnerIsLoggedOut} - /> - <MessageForm - disabled={isChatLocked} - chatAreaId={ELEMENT_ID} - chatTitle={title} - chatName={chatName} - minMessageLength={minMessageLength} - maxMessageLength={maxMessageLength} - handleSendMessage={actions.handleSendMessage} - /> - </div> - ); - } -} + <Icon iconName="close" onClick={() => actions.handleClosePrivateChat(chatID)} /> + </Link> : + <ChatDropdown /> + } + </header> + <MessageList + chatId={chatID} + messages={messages} + id={ELEMENT_ID} + scrollPosition={scrollPosition} + hasUnreadMessages={hasUnreadMessages} + handleScrollUpdate={actions.handleScrollUpdate} + handleReadMessage={actions.handleReadMessage} + lastReadMessageTime={lastReadMessageTime} + partnerIsLoggedOut={partnerIsLoggedOut} + /> + <MessageForm + disabled={isChatLocked} + chatAreaId={ELEMENT_ID} + chatTitle={title} + chatName={chatName} + minMessageLength={minMessageLength} + maxMessageLength={maxMessageLength} + handleSendMessage={actions.handleSendMessage} + /> + </div> + ); +}; + +export default injectWbResizeEvent(injectIntl(Chat)); -export default injectIntl(Chat); +Chat.propTypes = { + chatID: PropTypes.string.isRequired, + chatName: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + messages: PropTypes.arrayOf( + PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.array, + PropTypes.string, + PropTypes.number, + PropTypes.object, + ]), + ).isRequired, + ).isRequired, + scrollPosition: PropTypes.number, + hasUnreadMessages: PropTypes.bool.isRequired, + lastReadMessageTime: PropTypes.number.isRequired, + partnerIsLoggedOut: PropTypes.bool.isRequired, + isChatLocked: PropTypes.bool.isRequired, + minMessageLength: PropTypes.number.isRequired, + maxMessageLength: PropTypes.number.isRequired, + actions: PropTypes.shape({ + handleClosePrivateChat: PropTypes.func.isRequired, + handleReadMessage: PropTypes.func.isRequired, + handleScrollUpdate: PropTypes.func.isRequired, + handleSendMessage: PropTypes.func.isRequired, + }).isRequired, + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired, + }).isRequired, +}; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss index fc07712b5caa4c3d774c547526c3d27158253dcf..d40826f530dea7235222611fea94b571deacd6c4 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss @@ -70,6 +70,11 @@ .sendButton { margin-left: $sm-padding-x; + align-self: flex-end; + + i { + font-size: 115% !important; + } } .info { diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index b68a8d01f19c7de13f5a5437868f2b1c91208c3f..9f6f665d8329aacf411394be0c42f40ac5cb4a56 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -16,13 +16,31 @@ } .systemMessage { - .item + &, - & + .item { - margin-bottom: $line-height-computed * 1.5; + padding: $line-height-computed 0; + border-bottom: 1px solid $color-gray-lighter; + + .item:not(&) + & { + border-top: 1px solid $color-gray-lighter; + } + + & + & { + margin-top: -$line-height-computed; + } + + &:last-child { + border-bottom: none; } .message { - color: $color-heading; + color: $color-gray; + + + // hide the <br> we dont want from the default WelcomeMessage + br:first-child, + br:last-child, + br + br { + display: none; + } } } @@ -70,7 +88,6 @@ display: flex; min-width: 0; font-weight: 600; - color: $color-gray-light; text-transform: capitalize; font-style: italic; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss index 2cf89614b17b618ba2b32b2db1fd0a8cc1ca96c4..8e4af86678558bb1a5366391693313618bb818c6 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -11,9 +11,11 @@ $padding: $md-padding-x; position: relative; overflow-x: hidden; overflow-y: auto; + padding-left: $padding; + margin-left: -$padding; padding-right: $padding; - padding-bottom: $padding; margin-right: -$padding; + padding-bottom: $padding; margin-bottom: -$padding; } @@ -29,10 +31,11 @@ $padding: $md-padding-x; } .unreadButton { - flex-grow: 0; flex-shrink: 0; - border-radius: 0; + width: 100%; text-transform: uppercase; - margin-bottom: .5rem; + // margin-top: .25rem; + margin-bottom: .25rem; + background-color: darken($color-white, 5%); @extend %text-elipsis; } diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx index 6d16c52d426ef961707153bb21ab0b49817c94dd..9831e593e3acc68327c6fbdc9578f003a3f4f907 100644 --- a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx @@ -1,15 +1,29 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import styles from './styles.scss'; -import { findDOMNode } from 'react-dom'; -export default class ClosedCaptions extends React.Component { +class ClosedCaptions extends Component { constructor(props) { super(props); this.shouldScrollBottom = false; } + componentWillUpdate() { + const node = this.refCCScrollArea; + + // number 4 is for the border + // offset height includes the border, but scrollheight doesn't + this.shouldScrollBottom = (node.scrollTop + node.offsetHeight) - 4 === node.scrollHeight; + } + + componentDidUpdate() { + if (this.shouldScrollBottom) { + this.refCCScrollArea.scrollTop = this.refCCScrollArea.scrollHeight; + } + } + renderCaptions(caption) { const text = caption.captions; const captionStyles = { @@ -29,31 +43,6 @@ export default class ClosedCaptions extends React.Component { ); } - componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUpdate() { - const node = findDOMNode(this.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; - } - - componentDidUpdate() { - if (this.shouldScrollBottom) { - const node = findDOMNode(this.ccScrollArea); - node.scrollTop = node.scrollHeight; - } - } - render() { const { locale, @@ -64,10 +53,10 @@ export default class ClosedCaptions extends React.Component { return ( <div disabled className={styles.ccbox}> <div className={styles.title}> - <p> {locale || 'Locale is not selected'} </p> + <p> {locale} </p> </div> <div - ref={(ref) => { this.ccScrollArea = ref; }} + ref={(ref) => { this.refCCScrollArea = ref; }} className={styles.frame} style={{ background: backgroundColor }} > @@ -79,3 +68,27 @@ export default class ClosedCaptions extends React.Component { ); } } + +export default injectWbResizeEvent(ClosedCaptions); + +ClosedCaptions.propTypes = { + backgroundColor: PropTypes.string.isRequired, + captions: PropTypes.arrayOf( + PropTypes.shape({ + ownerId: PropTypes.string.isRequired, + captions: PropTypes.arrayOf( + PropTypes.shape({ + captions: PropTypes.string.isRequired, + }).isRequired, + ).isRequired, + }).isRequired, + ).isRequired, + locale: PropTypes.string.isRequired, + fontColor: PropTypes.string.isRequired, + fontSize: PropTypes.string.isRequired, + fontFamily: PropTypes.string.isRequired, +}; + +ClosedCaptions.defaultProps = { + locale: 'Locale is not selected', +}; diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx b/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx index a7ba7bf422e0b576938badc91f379c008438a05c..a8da014e7cb5ca165425f3ea9df6c2c6e36bfe18 100644 --- a/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/closed-captions/container.jsx @@ -1,21 +1,10 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import { createContainer } from 'meteor/react-meteor-data'; -import ClosedCaptionsService from './service.js'; +import ClosedCaptionsService from './service'; import ClosedCaptions from './component'; -class ClosedCaptionsContainer extends Component { - constructor(props) { - super(props); - } - - render() { - return ( - <ClosedCaptions {...this.props}> - {this.props.children} - </ClosedCaptions> - ); - } -} +const ClosedCaptionsContainer = props => ( + <ClosedCaptions {...props} /> + ); export default createContainer(() => ClosedCaptionsService.getCCData(), ClosedCaptionsContainer); diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/trigger/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/trigger/component.jsx index b2e3b07265ec01eca3b8e6d7a6dec184a3073b8e..a55cb8a379976891038a02d2af3d539ba4038ce9 100644 --- a/bigbluebutton-html5/imports/ui/components/dropdown/trigger/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/dropdown/trigger/component.jsx @@ -41,16 +41,14 @@ export default class DropdownTrigger extends Component { } render() { - const { children, style, className, tabIndex } = this.props; + const { children, className, ...restProps } = this.props; const TriggerComponent = React.Children.only(children); - const TriggerComponentBounded = React.cloneElement(children, { + const TriggerComponentBounded = React.cloneElement(TriggerComponent, { + ...restProps, onClick: this.handleClick, onKeyDown: this.handleKeyDown, 'aria-haspopup': true, - tabIndex, - style, - className: cx(children.props.className, className), }); diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss index 80aa5ef1338b1816635efaf89fd7f6cc8321d951..48ea6b771e74ff36608a91be320d7eca2e783102 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss @@ -46,11 +46,12 @@ content: ''; position: absolute; border-radius: 50%; - width: 10px; - height: 10px; - bottom: 0; - right: 0; + width: 12px; + height: 12px; + bottom: 2px; + right: 3px; background-color: $color-danger; + border: 2px solid; } } @@ -73,7 +74,6 @@ } .btnSettings { - transform: rotate(90deg); } .dropdownBreakout { diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx index 70d3585102ce7f71b49c3b73bf54666579d1541b..378d27ff5d6c29ed3b669376cb597728e617011c 100644 --- a/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/component.jsx @@ -1,6 +1,7 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import styles from './styles.scss'; const COLORS = [ @@ -8,6 +9,7 @@ const COLORS = [ ]; const propTypes = { + children: PropTypes.string.isRequired, color: PropTypes.oneOf(COLORS), }; @@ -15,31 +17,20 @@ const defaultProps = { color: 'default', }; -export default class NotificationsBar extends Component { - - componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - render() { - const { color } = this.props; - - return ( - <div - role="alert" - className={cx(styles.notificationsBar, styles[color])} - > - {this.props.children} - </div> - ); - } -} +const NotificationsBar = (props) => { + const { color } = props; + + return ( + <div + role="alert" + className={cx(styles.notificationsBar, styles[color])} + > + {props.children} + </div> + ); +}; NotificationsBar.propTypes = propTypes; NotificationsBar.defaultProps = defaultProps; + +export default injectWbResizeEvent(NotificationsBar); diff --git a/bigbluebutton-html5/imports/ui/components/polling/component.jsx b/bigbluebutton-html5/imports/ui/components/polling/component.jsx index 99edf9551500a93f8602e32ca9f183ff0acac8ea..97fa2bf55013d83aa87e723f71f1859f79f9f346 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/polling/component.jsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import Button from '/imports/ui/components/button/component'; -import styles from './styles.scss'; import { defineMessages, injectIntl } from 'react-intl'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; +import styles from './styles.scss'; const intlMessages = defineMessages({ pollingTitleLabel: { @@ -10,20 +12,7 @@ const intlMessages = defineMessages({ }, }); -class PollingComponent extends React.Component { - constructor(props) { - super(props); - } - - componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } +class PollingComponent extends Component { getStyles() { const number = this.props.poll.answers.length + 1; @@ -49,9 +38,9 @@ class PollingComponent extends React.Component { {intl.formatMessage(intlMessages.pollingTitleLabel)} </p> </div> - {poll.answers.map((pollAnswer, index) => + {poll.answers.map(pollAnswer => (<div - key={index} + key={pollAnswer.id} style={calculatedStyles} className={styles.pollButtonWrapper} > @@ -83,4 +72,20 @@ class PollingComponent extends React.Component { } } -export default injectIntl(PollingComponent); +export default injectWbResizeEvent(injectIntl(PollingComponent)); + +PollingComponent.propTypes = { + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired, + }).isRequired, + handleVote: PropTypes.func.isRequired, + poll: PropTypes.shape({ + pollId: PropTypes.string.isRequired, + answers: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + key: PropTypes.string.isRequired, + }).isRequired, + ).isRequired, + }).isRequired, +}; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index e9e5cb35a319d3f397441712b9f3abddb5884f85..a5ea9298fc39db3850200bbb774a9e1b92f32d28 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'; import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteboard-overlay/container'; @@ -12,7 +12,7 @@ import Slide from './slide/component'; import styles from './styles.scss'; -export default class PresentationArea extends React.Component { +export default class PresentationArea extends Component { constructor() { super(); @@ -59,7 +59,7 @@ export default class PresentationArea extends React.Component { // if a user is a presenter - this means there is a whiteboard toolbar on the right // and we have to get the width/height of the refWhiteboardArea // (inner hidden div with absolute position) - if (this.props.userIsPresenter) { + if (this.props.userIsPresenter || this.props.multiUser) { clientHeight = refWhiteboardArea.clientHeight; clientWidth = refWhiteboardArea.clientWidth; } @@ -132,19 +132,19 @@ export default class PresentationArea extends React.Component { // renders the whole presentation area renderPresentationArea() { // sometimes tomcat publishes the slide url, but the actual file is not accessible (why?) - if (this.props.currentSlide && - this.props.currentSlide.calculatedData && - this.props.currentSlide.calculatedData.width && - this.props.currentSlide.calculatedData.height) { + if (!this.props.currentSlide || + !this.props.currentSlide.calculatedData) { + return null; + } // to control the size of the svg wrapper manually // and adjust cursor's thickness, so that svg didn't scale it automatically - const adjustedSizes = this.calculateSize(); + const adjustedSizes = this.calculateSize(); // a reference to the slide object - const slideObj = this.props.currentSlide; + const slideObj = this.props.currentSlide; // retrieving the pre-calculated data from the slide object - const { + const { x, y, width, @@ -154,70 +154,68 @@ export default class PresentationArea extends React.Component { imageUri, } = slideObj.calculatedData; - return ( - <div - style={{ - width: adjustedSizes.width, - height: adjustedSizes.height, - WebkitTransition: 'width 0.2s', /* Safari */ - transition: 'width 0.2s', + return ( + <div + style={{ + width: adjustedSizes.width, + height: adjustedSizes.height, + WebkitTransition: 'width 0.2s', /* Safari */ + transition: 'width 0.2s', + }} + > + <CSSTransitionGroup + transitionName={{ + enter: styles.enter, + enterActive: styles.enterActive, + appear: styles.appear, + appearActive: styles.appearActive, }} + transitionAppear + transitionEnter + transitionLeave={false} + transitionAppearTimeout={400} + transitionEnterTimeout={400} + transitionLeaveTimeout={400} > - <CSSTransitionGroup - transitionName={{ - enter: styles.enter, - enterActive: styles.enterActive, - appear: styles.appear, - appearActive: styles.appearActive, - }} - transitionAppear - transitionEnter - transitionLeave={false} - transitionAppearTimeout={400} - transitionEnterTimeout={400} - transitionLeaveTimeout={400} + <svg + width={width} + height={height} + ref={(ref) => { if (ref != null) { this.svggroup = ref; } }} + viewBox={`${x} ${y} ${viewBoxWidth} ${viewBoxHeight}`} + version="1.1" + xmlns="http://www.w3.org/2000/svg" + className={styles.svgStyles} + key={slideObj.id} > - <svg - width={width} - height={height} - ref={(ref) => { if (ref != null) { this.svggroup = ref; } }} - viewBox={`${x} ${y} ${viewBoxWidth} ${viewBoxHeight}`} - version="1.1" - xmlns="http://www.w3.org/2000/svg" - className={styles.svgStyles} - key={slideObj.id} - > - <defs> - <clipPath id="viewBox"> - <rect x={x} y={y} width="100%" height="100%" fill="none" /> - </clipPath> - </defs> - <g clipPath="url(#viewBox)"> - <Slide - id="slideComponent" - imageUri={imageUri} - svgWidth={width} - svgHeight={height} - /> - <AnnotationGroupContainer - width={width} - height={height} - whiteboardId={slideObj.id} - /> - <CursorWrapperContainer - widthRatio={slideObj.widthRatio} - physicalWidthRatio={adjustedSizes.width / width} - slideWidth={width} - slideHeight={height} - /> - </g> - {this.renderOverlays(slideObj, adjustedSizes)} - </svg> - </CSSTransitionGroup> - </div> - ); - } - return null; + <defs> + <clipPath id="viewBox"> + <rect x={x} y={y} width="100%" height="100%" fill="none" /> + </clipPath> + </defs> + <g clipPath="url(#viewBox)"> + <Slide + id="slideComponent" + imageUri={imageUri} + svgWidth={width} + svgHeight={height} + /> + <AnnotationGroupContainer + width={width} + height={height} + whiteboardId={slideObj.id} + /> + <CursorWrapperContainer + widthRatio={slideObj.widthRatio} + physicalWidthRatio={adjustedSizes.width / width} + slideWidth={width} + slideHeight={height} + /> + </g> + {this.renderOverlays(slideObj, adjustedSizes)} + </svg> + </CSSTransitionGroup> + </div> + ); } renderOverlays(slideObj, adjustedSizes) { @@ -271,17 +269,18 @@ export default class PresentationArea extends React.Component { } renderWhiteboardToolbar() { - if (this.props.currentSlide) { - const adjustedSizes = this.calculateSize(); - - return ( - <WhiteboardToolbarContainer - whiteboardId={this.props.currentSlide.id} - height={adjustedSizes.height} - /> - ); + if (!this.props.currentSlide || + !this.props.currentSlide.calculatedData) { + return null; } - return null; + + const adjustedSizes = this.calculateSize(); + return ( + <WhiteboardToolbarContainer + whiteboardId={this.props.currentSlide.id} + height={adjustedSizes.height} + /> + ); } render() { @@ -329,7 +328,7 @@ PresentationArea.propTypes = { viewBoxWidth: PropTypes.number.isRequired, viewBoxHeight: PropTypes.number.isRequired, imageUri: PropTypes.string.isRequired, - }).isRequired, + }), }), // current multi-user status multiUser: PropTypes.bool.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx index fc3bca1968b8dc286ad7f6da8fc9820abad400b1..a45c819198873819ff534ca223e081c503bd7eac 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx @@ -3,7 +3,7 @@ import { createContainer } from 'meteor/react-meteor-data'; import PresentationAreaService from './service'; import PresentationArea from './component'; -const PresentationAreaContainer = ({ ...props }) => ( +const PresentationAreaContainer = props => ( <PresentationArea {...props} /> ); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx index e15af7ab1711270795d2508e37773fd13a95eb53..ada7003b6f496ee917c7f809d4de1b69e4566f83 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/component.jsx @@ -3,20 +3,18 @@ import PropTypes from 'prop-types'; export default class Cursor extends Component { - static scale(attribute, propsObj) { - const { widthRatio, physicalWidthRatio } = propsObj; + static scale(attribute, widthRatio, physicalWidthRatio) { return ((attribute * widthRatio) / 100) / physicalWidthRatio; } - static invertScale(attribute, propsObj) { - const { widthRatio, physicalWidthRatio } = propsObj; + static invertScale(attribute, widthRatio, physicalWidthRatio) { return ((attribute * physicalWidthRatio) * 100) / widthRatio; } - static getCursorCoordinates(props) { + static getCursorCoordinates(cursorX, cursorY, slideWidth, slideHeight) { // main cursor x and y coordinates - const x = (props.cursorX / 100) * props.slideWidth; - const y = (props.cursorY / 100) * props.slideHeight; + const x = (cursorX / 100) * slideWidth; + const y = (cursorY / 100) * slideHeight; return { x, @@ -24,9 +22,7 @@ export default class Cursor extends Component { }; } - static getFillAndLabel(props) { - const { presenter, isMultiUser } = props; - + static getFillAndLabel(presenter, isMultiUser) { const obj = { fill: 'green', displayLabel: false, @@ -69,15 +65,20 @@ export default class Cursor extends Component { } componentWillMount() { - const cursorCoordinate = Cursor.getCursorCoordinates(this.props); - const { fill, displayLabel } = Cursor.getFillAndLabel(this.props); - const _scaledSizes = Cursor.getScaledSizes(this.props); + const { cursorX, cursorY, slideWidth, slideHeight, presenter, isMultiUser } = this.props; // setting the initial cursor info - this.cursorCoordinate = cursorCoordinate; + this.scaledSizes = Cursor.getScaledSizes(this.props); + this.cursorCoordinate = Cursor.getCursorCoordinates( + cursorX, + cursorY, + slideWidth, + slideHeight, + ); + + const { fill, displayLabel } = Cursor.getFillAndLabel(presenter, isMultiUser); this.fill = fill; this.displayLabel = displayLabel; - this.scaledSizes = _scaledSizes; } componentDidMount() { @@ -98,7 +99,10 @@ export default class Cursor extends Component { } = this.props; if (presenter !== nextProps.presenter || isMultiUser !== nextProps.isMultiUser) { - const { fill, displayLabel } = Cursor.getFillAndLabel(nextProps); + const { fill, displayLabel } = Cursor.getFillAndLabel( + nextProps.presenter, + nextProps.isMultiUser, + ); this.displayLabel = displayLabel; this.fill = fill; } @@ -108,12 +112,16 @@ export default class Cursor extends Component { || (labelBoxWidth !== nextProps.labelBoxWidth || labelBoxHeight !== nextProps.labelBoxHeight)) { - const _scaledSizes = Cursor.getScaledSizes(nextProps); - this.scaledSizes = _scaledSizes; + this.scaledSizes = Cursor.getScaledSizes(nextProps); } if (cursorX !== nextProps.cursorX || cursorY !== nextProps.cursorY) { - const cursorCoordinate = Cursor.getCursorCoordinates(nextProps); + const cursorCoordinate = Cursor.getCursorCoordinates( + nextProps.cursorX, + nextProps.cursorY, + nextProps.slideWidth, + nextProps.slideHeight, + ); this.cursorCoordinate = cursorCoordinate; } } @@ -125,9 +133,10 @@ export default class Cursor extends Component { if (this.cursorLabelRef) { const { width, height } = this.cursorLabelRef.getBBox(); + const { widthRatio, physicalWidthRatio } = this.props; - labelBoxWidth = Cursor.invertScale(width, this.props); - labelBoxHeight = Cursor.invertScale(height, this.props); + labelBoxWidth = Cursor.invertScale(width, widthRatio, physicalWidthRatio); + labelBoxHeight = Cursor.invertScale(height, widthRatio, physicalWidthRatio); // if the width of the text node is bigger than the maxSize - set the width to maxWidth if (labelBoxWidth > this.props.cursorLabelBox.maxWidth) { @@ -235,6 +244,11 @@ Cursor.propTypes = { // Slide physical size to original size ratio physicalWidthRatio: PropTypes.number.isRequired, + // Slide width (svg) + slideWidth: PropTypes.number.isRequired, + // Slide height (svg) + slideHeight: PropTypes.number.isRequired, + /** * Defines the cursor radius (not scaled) * @defaultValue 5 @@ -267,7 +281,6 @@ Cursor.propTypes = { Cursor.defaultProps = { radius: 5, - cursorLabelText: { // text Y shift (10 points down) textDY: 10, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx index f766bbc5e98fdc9fee20a1f0f62e141c5e808f53..96c2d724d8f5855605e543878c654de9235881b3 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/container.jsx @@ -29,16 +29,14 @@ const CursorWrapperContainer = ({ presenterCursorId, multiUserCursorIds, ...rest /> : null } - {multiUserCursorIds.length > 0 ? - multiUserCursorIds.map(cursorId => - (<CursorContainer - key={cursorId._id} - cursorId={cursorId._id} - presenter={false} - {...rest} - />), - ) - : null } + {multiUserCursorIds.map(cursorId => + (<CursorContainer + key={cursorId._id} + cursorId={cursorId._id} + presenter={false} + {...rest} + />), + )} </g> ); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/service.js b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/service.js index 6540751b80e75872322aa61f9d8655dc1383571a..bee7d97f56c35765e8f962c6933d310318fb4f5f 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/cursor-wrapper-container/service.js @@ -5,12 +5,7 @@ import Users from '/imports/api/2.0/users'; const getMultiUserStatus = () => { const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID }); - - if (data) { - return data.multiUser; - } - - return false; + return data ? data.multiUser : false; }; const getPresenterCursorId = userId => Cursor.findOne({ userId }, { fields: { _id: 1 } }); @@ -30,18 +25,24 @@ const getCurrentCursorIds = () => { // checking whether multiUser mode is on or off const isMultiUser = getMultiUserStatus(); - if (isMultiUser && data.presenterCursorId) { - // it's a multi-user mode - fetching all the cursors except the presenter's - const selector = { - _id: { - $ne: data.presenterCursorId._id, - }, - }; + // it's a multi-user mode - fetching all the cursors except the presenter's + if (isMultiUser) { + let selector = {}; const filter = { fields: { _id: 1, }, }; + + // if there is a presenter cursor - excluding it from the query + if (data.presenterCursorId) { + selector = { + _id: { + $ne: data.presenterCursorId._id, + }, + }; + } + data.multiUserCursorIds = Cursor.find(selector, filter).fetch(); } else { // it's not multi-user, assigning an empty array diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx index 6e9eab3d1a7784694480110527976c40104725d6..a5412544b0461e8d1b4001653b31d5a4ed6b810b 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import Button from '/imports/ui/components/button/component'; import styles from './styles.scss'; @@ -137,16 +138,6 @@ class PresentationToolbar extends Component { this.handleValuesChange = this.handleValuesChange.bind(this); } - componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - handleValuesChange(event) { this.setState({ sliderValue: event.target.value }); } @@ -279,6 +270,9 @@ PresentationToolbar.propTypes = { previousSlideHandler: PropTypes.func.isRequired, skipToSlideHandler: PropTypes.func.isRequired, }).isRequired, + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired, + }).isRequired, }; -export default injectIntl(PresentationToolbar); +export default injectWbResizeEvent(injectIntl(PresentationToolbar)); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx index a182e102c51f54dca9546dc801c906caca984d61..e414e27505f36303a80b5aaa1ccbd20d144a81ef 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx @@ -4,7 +4,7 @@ import { createContainer } from 'meteor/react-meteor-data'; import PresentationToolbarService from './service'; import PresentationToolbar from './component'; -const PresentationToolbarContainer = ({ ...props }) => { +const PresentationToolbarContainer = (props) => { const { currentSlideNum, userIsPresenter, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js index 35e0ff64c3be99fb0dd5c2985569b218fc3cd05b..b57d5226df1e96ec46b8eba4a7552e5f0510dc94 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/service.js @@ -16,10 +16,7 @@ const getSlideData = (params) => { userId, }); - let userIsPresenter; - if (currentUser) { - userIsPresenter = currentUser.presenter; - } + const userIsPresenter = currentUser ? currentUser.presenter : false; // Get total number of slides in this presentation const numberOfSlides = Slides.find({ diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/styles.scss old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/presentation/resize-wrapper/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/resize-wrapper/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c769c78d17b96ad394cfe25a8336aabe962fd118 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/presentation/resize-wrapper/component.jsx @@ -0,0 +1,20 @@ +import React, { Component } from 'react'; + +const injectWbResizeEvent = WrappedComponent => + class Resize extends Component { + componentDidMount() { + window.dispatchEvent(new Event('resize')); + } + + componentWillUnmount() { + window.dispatchEvent(new Event('resize')); + } + + render() { + return ( + <WrappedComponent {...this.props} /> + ); + } + }; + +export default injectWbResizeEvent; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index 3464eb9c66be028eec916c8260ea00693947632e..d7edc9b7b9f4f276a090c17eedbc779251d8c35f 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -34,22 +34,12 @@ const getCurrentSlide = () => { const isPresenter = () => { const currentUser = Users.findOne({ userId: Auth.userID }); - - if (currentUser) { - return currentUser.presenter; - } - - return false; + return currentUser ? currentUser.presenter : false; }; const getMultiUserStatus = () => { const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID }); - - if (data) { - return data.multiUser; - } - - return false; + return data ? data.multiUser : false; }; export default { diff --git a/bigbluebutton-html5/imports/ui/components/presentation/slide/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/slide/component.jsx index bbbfc28b7839ff5bc3b07687acf5bec6d1bd0225..702977d2d9fa56c9fd1b575c584738ce735439e1 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/slide/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/slide/component.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Slide = ({ ...props }) => { +const Slide = (props) => { const { imageUri, svgWidth, svgHeight } = props; return ( diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index fda261c824c23b0065afa8b13679f4b3eb73abce..cce5d435ab257f75dfac23870b0feacc2ccc8fc7 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -2,12 +2,12 @@ import React from 'react'; import Modal from 'react-modal'; import Icon from '/imports/ui/components/icon/component'; import Button from '/imports/ui/components/button/component'; -import BaseMenu from '../base/component'; import ReactDOM from 'react-dom'; import cx from 'classnames'; -import styles from '../styles.scss'; import Toggle from '/imports/ui/components/switch/component'; import { defineMessages, injectIntl } from 'react-intl'; +import BaseMenu from '../base/component'; +import styles from '../styles'; const MIN_FONTSIZE = 0; const MAX_FONTSIZE = 4; @@ -96,7 +96,8 @@ class ApplicationMenu extends BaseMenu { const currentFontSize = this.state.settings.fontSize; const availableFontSizes = this.props.fontSizes; const canIncreaseFontSize = availableFontSizes.indexOf(currentFontSize) < MAX_FONTSIZE; - const fs = (canIncreaseFontSize) ? availableFontSizes.indexOf(currentFontSize) + 1 : MAX_FONTSIZE; + const fs = (canIncreaseFontSize) ? + availableFontSizes.indexOf(currentFontSize) + 1 : MAX_FONTSIZE; this.changeFontSize(availableFontSizes[fs]); } @@ -104,7 +105,8 @@ class ApplicationMenu extends BaseMenu { const currentFontSize = this.state.settings.fontSize; const availableFontSizes = this.props.fontSizes; const canDecreaseFontSize = availableFontSizes.indexOf(currentFontSize) > MIN_FONTSIZE; - const fs = (canDecreaseFontSize) ? availableFontSizes.indexOf(currentFontSize) - 1 : MIN_FONTSIZE; + const fs = (canDecreaseFontSize) ? + availableFontSizes.indexOf(currentFontSize) - 1 : MIN_FONTSIZE; this.changeFontSize(availableFontSizes[fs]); } @@ -148,6 +150,7 @@ class ApplicationMenu extends BaseMenu { </div> </div> </div> + {/* TODO: Uncomment after the release <div className={styles.row}> <div className={styles.col}> <div className={styles.formElement} > @@ -167,7 +170,7 @@ class ApplicationMenu extends BaseMenu { /> </div> </div> - </div> + </div>*/} <div className={styles.row}> <div className={styles.col}> <div className={styles.formElement}> diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss index 05ae6f536485a942ec7f5fcb7c72336bfe91da7c..c636ab4f96c6dbec45fdf3c9fa89e8ab6350d89b 100644 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss @@ -5,7 +5,7 @@ */ $user-avatar-border: $color-gray-light; $user-avatar-text: $color-white; -$user-indicators-offset: -.15rem; +$user-indicators-offset: -5px; $user-indicator-presenter-bg: $color-primary; $user-indicator-voice-bg: $color-success; $user-indicator-muted-bg: $color-danger; @@ -20,7 +20,7 @@ $user-list-bg: #F3F6F9; text-align: center; text-transform: capitalize; transition: .3s ease-in-out; - font-size: 1rem; + font-size: .85rem; &:after, &:before { content: ""; @@ -31,9 +31,9 @@ $user-list-bg: #F3F6F9; color: inherit; top: auto; left: auto; - bottom: $user-indicators-offset / 2; + bottom: $user-indicators-offset; right: $user-indicators-offset; - border: 1.25px solid $user-list-bg; + border: 1.5px solid $user-list-bg; border-radius: 50%; background-color: $user-indicator-voice-bg; color: $user-avatar-text; @@ -60,7 +60,7 @@ $user-list-bg: #F3F6F9; &:before { content: "\00a0\e90b\00a0"; opacity: 1; - top: $user-indicators-offset / 2; + top: $user-indicators-offset; left: $user-indicators-offset; bottom: auto; right: auto; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss index a507cf4c17924278f9654f26ed0b52703f03aea8..caac495cda7d2dcabaab84eec44b78531e87fb16 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/styles.scss @@ -6,6 +6,7 @@ cursor: pointer; padding: 0; text-decoration: none; + color: $color-gray-dark; } .chatListItemLink { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index 6091961f5cdfbf49715382d701595d5a15371a43..5b4d61c507fa8c0795fd7b01608f9f3cd436bbba 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -2,6 +2,8 @@ import React, { Component } from 'react'; import { injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; + +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import styles from './styles'; import UserListHeader from './user-list-header/component'; import UserContent from './user-list-content/component'; @@ -52,10 +54,10 @@ class UserList extends Component { render() { return ( <div className={styles.userList}> - <UserListHeader + {/* <UserListHeader intl={this.props.intl} compact={this.state.compact} - /> + /> */} {<UserContent intl={this.props.intl} openChats={this.props.openChats} @@ -82,4 +84,4 @@ class UserList extends Component { UserList.propTypes = propTypes; UserList.defaultProps = defaultProps; -export default withRouter(injectIntl(UserList)); +export default withRouter(injectWbResizeEvent(injectIntl(UserList))); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js old mode 100755 new mode 100644 diff --git a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss index f40d05a25accc5f7f184809ef7b7b2fcc7a4fbda..b7d24ee75f56d607a8e2bf6d2d52f70a740fefe1 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/styles.scss @@ -61,6 +61,7 @@ $user-icons-color-hover: $color-gray; background-color: $user-list-bg; color: $user-list-text; height: 100vh; + padding-top: $md-padding-x; } .lists { @@ -77,15 +78,6 @@ $user-icons-color-hover: $color-gray; flex-shrink: 1; } -.smallTitle { - font-size: 0.85rem; - font-weight: 600; - text-transform: uppercase; - padding: 0 $sm-padding-x; - margin: 0 0 ($lg-padding-x / 2) 0; - color: $color-gray-light; -} - .participants, .messages { flex-grow: 0; display: flex; @@ -94,7 +86,6 @@ $user-icons-color-hover: $color-gray; } .messages { - margin-bottom: $lg-padding-x; } .participants { @@ -105,6 +96,15 @@ $user-icons-color-hover: $color-gray; overflow-y: auto; } +.smallTitle { + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + padding: 0 $sm-padding-x; + margin: ($md-padding-x / 2) 0 0 0; + color: $color-gray-light; +} + .enter, .appear { opacity: 0.01; } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss index 560ffd1881df487299c7229ba00318843e4c0dd9..77695e3c8f347e23390e21a83a19d197307fd201 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/styles.scss @@ -1,4 +1,5 @@ @import "/imports/ui/components/user-list/styles.scss"; +@import "/imports/ui/stylesheets/mixins/_scrollable"; .content { @extend %flex-column; @@ -8,4 +9,5 @@ .scrollableList { @extend %customListFocus; -} \ No newline at end of file + @include scrollbox-vertical(); +} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/styles.scss b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/styles.scss index e97a9ea3a9d3fc979cd95dc96abe5d2df8d688ee..3f3cc128b073e709889776ac4cff34d88ec2cf5a 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/styles.scss @@ -1,12 +1,7 @@ @import "/imports/ui/components/user-list/styles.scss"; .smallTitle { - font-size: 0.85rem; - font-weight: 600; - text-transform: uppercase; - padding: 0 $sm-padding-x; - margin: 0 0 ($lg-padding-x / 2) 0; - color: $color-gray-light; + @extend .smallTitle; } .scrollableList { @@ -25,4 +20,4 @@ @extend .lists; overflow-x: hidden; flex-shrink: 1; -} \ No newline at end of file +} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx index cd458b47d223e2781e6dd5010016e820ae3d68dd..e3100b4bd5af036fdbbb54ac8a079c21498f652e 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx @@ -96,16 +96,26 @@ const intlMessages = defineMessages({ class UserParticipants extends Component { + constructor() { + super(); + + this.getScrollContainerRef = this.getScrollContainerRef.bind(this); + } + componentDidMount() { if (!this.props.compact) { - this._usersList.addEventListener('keydown', + this.refScrollContainer.addEventListener('keydown', event => this.props.rovingIndex(event, - this._usersList, - this._userItems, + this.refScrollContainer, + this.refScrollItems, this.props.users.length)); } } + getScrollContainerRef() { + return this.refScrollContainer; + } + render() { const { users, @@ -181,7 +191,8 @@ class UserParticipants extends Component { className={styles.scrollableList} role="tabpanel" tabIndex={0} - ref={(ref) => { this._usersList = ref; }} + refScrollContainer + ref={(ref) => { this.refScrollContainer = ref; }} > <CSSTransitionGroup transitionName={listTransition} @@ -194,7 +205,7 @@ class UserParticipants extends Component { component="div" className={cx(styles.participantsList, styles.scrollableList)} > - <div ref={(ref) => { this._userItems = ref; }}> + <div ref={(ref) => { this.refScrollItems = ref; }}> { users.map(user => ( <UserListItem @@ -208,6 +219,7 @@ class UserParticipants extends Component { getAvailableActions={getAvailableActions} normalizeEmojiName={normalizeEmojiName} isMeetingLocked={isMeetingLocked} + getScrollContainerRef={this.getScrollContainerRef} /> )) } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx old mode 100755 new mode 100644 index de8a636db6c630b1d81c11a9e5f0273071d6fa41..7310566300424a5fa984fb128afa5171ffd1ac94 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx @@ -30,6 +30,7 @@ const propTypes = { meeting: PropTypes.shape({}).isRequired, isMeetingLocked: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired, + getScrollContainerRef: PropTypes.func.isRequired, }; const defaultProps = { @@ -105,6 +106,7 @@ class UserListItem extends Component { meeting, isMeetingLocked, normalizeEmojiName, + getScrollContainerRef, } = this.props; const actions = this.getUsersActions(); @@ -117,6 +119,7 @@ class UserListItem extends Component { actions={actions} meeting={meeting} isMeetingLocked={isMeetingLocked} + getScrollContainerRef={getScrollContainerRef} />); return contents; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx index 184da45328b6c01ae178a86879b4e6d54ef6a52f..9af1a7808be463022222e4aa582b5e3106c32d82 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-list-content/component.jsx @@ -52,6 +52,7 @@ const propTypes = { actions: PropTypes.arrayOf(PropTypes.shape({})).isRequired, meeting: PropTypes.shape({}).isRequired, isMeetingLocked: PropTypes.func.isRequired, + getScrollContainerRef: PropTypes.func.isRequired, }; @@ -90,7 +91,7 @@ class UserListContent extends Component { onActionsShow() { const dropdown = findDOMNode(this.dropdown); - const scrollContainer = dropdown.parentElement.parentElement; + const scrollContainer = this.props.getScrollContainerRef(); const dropdownTrigger = dropdown.children[0]; this.setState({ @@ -109,7 +110,8 @@ class UserListContent extends Component { dropdownVisible: false, }); - findDOMNode(this).parentElement.removeEventListener('scroll', this.handleScroll, false); + const scrollContainer = this.props.getScrollContainerRef(); + scrollContainer.removeEventListener('scroll', this.handleScroll, false); } getDropdownMenuParent() { @@ -131,7 +133,7 @@ class UserListContent extends Component { const dropdownTrigger = dropdown.children[0]; const dropdownContent = dropdown.children[1]; - const scrollContainer = dropdown.parentElement.parentElement; + const scrollContainer = this.props.getScrollContainerRef(); const nextState = { dropdownVisible: true, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx index a0fb86b829cbbec866d9664666ef450c2ce7c68e..4de6fad4d8aba6ef7a68c3ba3dbb14a3f7510dd2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/component.jsx @@ -10,6 +10,9 @@ import Text from '../annotations/text/container'; import Triangle from '../annotations/triangle/component'; import Pencil from '../annotations/pencil/component'; +const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations; +const DRAW_END = ANNOTATION_CONFIG.status.end; + export default class AnnotationFactory extends Component { static renderStaticAnnotation(annotationInfo, slideWidth, slideHeight, drawObject) { @@ -44,7 +47,7 @@ export default class AnnotationFactory extends Component { renderAnnotation(annotationInfo) { const drawObject = this.props.annotationSelector[annotationInfo.annotationType]; - if (annotationInfo.status === 'DRAW_END') { + if (annotationInfo.status === DRAW_END) { return AnnotationFactory.renderStaticAnnotation( annotationInfo, this.props.slideWidth, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/component.jsx index 1db318f2773b9a6b41418334da6f6040649e6d71..d6a3b60b5d38a90efaf3a0a795f87a17554af1ea 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/component.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const ReactiveAnnotation = ({ ...props }) => { +const ReactiveAnnotation = (props) => { const Component = props.drawObject; return ( diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx index 7feadea8d72203a6f1d548dd2bbd7f29012506b9..9124ab46ec73e3126a0da66fa889927339c69c9a 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx @@ -4,7 +4,7 @@ import { createContainer } from 'meteor/react-meteor-data'; import ReactiveAnnotationService from './service'; import ReactiveAnnotation from './component'; -const ReactiveAnnotationContainer = ({ ...props }) => { +const ReactiveAnnotationContainer = (props) => { if (props.annotation) { return ( <ReactiveAnnotation diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/component.jsx index 2e829dc27419e01ca78b1623796c6f743c8e1e37..3211a6caf61339fd0a3e66ad79416aa149b190a2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/component.jsx @@ -4,7 +4,7 @@ import StaticAnnotationService from './service'; export default class StaticAnnotation extends React.Component { - // finished Annotations (status == 'DRAW_END' ) should never update + // completed annotations should never update shouldComponentUpdate() { return false; } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/component.jsx index 34ebb2b4910cc03348ab0890d8f36cc467d5f850..05da443af91a3c5612da535eb9b8bb080d6f6ddc 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/component.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import AnnotationFactory from '../annotation-factory/component'; -const AnnotationGroup = ({ ...props }) => ( +const AnnotationGroup = props => ( <AnnotationFactory annotationsInfo={props.annotationsInfo} slideWidth={props.slideWidth} diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx index 13177654112e9a47702701e5363c75b7376ee627..53f3fecc97d31ccd05f1e58d19f7225bfd7d367c 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx @@ -4,7 +4,7 @@ import { createContainer } from 'meteor/react-meteor-data'; import AnnotationGroupService from './service'; import AnnotationGroup from './component'; -const AnnotationGroupContainer = ({ ...props }) => ( +const AnnotationGroupContainer = props => ( <AnnotationGroup annotationsInfo={props.annotationsInfo} slideWidth={props.width} diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx index da99ab09d7e2b85cf11c4d734e4601b9e3783092..c7c0e963bbe06c6f613fed441993f74f61c7e31b 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx @@ -2,6 +2,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import AnnotationHelpers from '../helpers'; +const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end; + export default class TextDrawComponent extends Component { static getViewerStyles(results) { const styles = { @@ -53,7 +55,7 @@ export default class TextDrawComponent extends Component { } componentDidMount() { - if (this.props.isActive && this.props.annotation.status !== 'DRAW_END') { + if (this.props.isActive && this.props.annotation.status !== DRAW_END) { this.handleFocus(); } } @@ -177,7 +179,7 @@ export default class TextDrawComponent extends Component { render() { const results = this.getCoordinates(); - if (this.props.isActive && this.props.annotation.status !== 'DRAW_END') { + if (this.props.isActive && this.props.annotation.status !== DRAW_END) { return this.renderPresenterTextShape(results); } return this.renderViewerTextShape(results); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx index 1c3e38f55043d0e1f691ae2b05feb9816797a49a..b58769135eca5a864281be91a64b7fb4885aa9de 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/container.jsx @@ -3,7 +3,7 @@ import { createContainer } from 'meteor/react-meteor-data'; import TextShapeService from './service'; import TextDrawComponent from './component'; -const TextDrawContainer = ({ ...props }) => ( +const TextDrawContainer = props => ( <TextDrawComponent {...props} /> ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js index 9bad9191b4a239233c47631083298e6192cbb321..0a319b60bda39699f3dc74d76336272625b23a53 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js @@ -3,50 +3,37 @@ import Users from '/imports/api/2.0/users'; import Auth from '/imports/ui/services/auth'; import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/'; +const DRAW_SETTINGS = 'drawSettings'; + const setTextShapeValue = (text) => { - const drawSettings = Storage.getItem('drawSettings'); + const drawSettings = Storage.getItem(DRAW_SETTINGS); if (drawSettings) { drawSettings.textShape.textShapeValue = text; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); + Storage.setItem(DRAW_SETTINGS, drawSettings); } }; const resetTextShapeActiveId = () => { - const drawSettings = Storage.getItem('drawSettings'); + const drawSettings = Storage.getItem(DRAW_SETTINGS); if (drawSettings) { drawSettings.textShape.textShapeActiveId = ''; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); + Storage.setItem(DRAW_SETTINGS, drawSettings); } }; const isPresenter = () => { const currentUser = Users.findOne({ userId: Auth.userID }); - - if (currentUser) { - return currentUser.presenter; - } - - return false; + return currentUser ? currentUser.presenter : false; }; const getMultiUserStatus = () => { const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID }); - - if (data) { - return data.multiUser; - } - - return false; + return data ? data.multiUser : false; }; const activeTextShapeId = () => { - const drawSettings = Storage.getItem('drawSettings'); - - if (drawSettings) { - return drawSettings.textShape.textShapeActiveId; - } - - return ''; + const drawSettings = Storage.getItem(DRAW_SETTINGS); + return drawSettings ? drawSettings.textShape.textShapeActiveId : ''; }; export default { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx index 193e7e89022483d58fd29f58232127d48d25bc65..6db90e596807ee7bf6529ab8e974de4a2ffe56b8 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import WhiteboardOverlayService from './service'; import WhiteboardOverlay from './component'; -const WhiteboardOverlayContainer = ({ ...props }) => { +const WhiteboardOverlayContainer = (props) => { if (Object.keys(props.drawSettings).length > 0) { return ( <WhiteboardOverlay {...props} /> @@ -23,20 +23,23 @@ export default createContainer(() => ({ WhiteboardOverlayContainer.propTypes = { - drawSettings: PropTypes.shape({ - // Annotation color - color: PropTypes.number.isRequired, - // Annotation thickness (not normalized) - thickness: PropTypes.number.isRequired, - // The name of the tool currently selected - tool: PropTypes.string.isRequired, - // Font size for the text shape - textFontSize: PropTypes.number.isRequired, - // Current active text shape value - textShapeValue: PropTypes.string.isRequired, - // Text active text shape id - textShapeActiveId: PropTypes.string.isRequired, - }), + drawSettings: PropTypes.oneOfType([ + PropTypes.shape({ + // Annotation color + color: PropTypes.number.isRequired, + // Annotation thickness (not normalized) + thickness: PropTypes.number.isRequired, + // The name of the tool currently selected + tool: PropTypes.string.isRequired, + // Font size for the text shape + textFontSize: PropTypes.number.isRequired, + // Current active text shape value + textShapeValue: PropTypes.string.isRequired, + // Text active text shape id + textShapeActiveId: PropTypes.string.isRequired, + }), + PropTypes.object.isRequired, + ]), }; WhiteboardOverlayContainer.defaultProps = { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx index 774c7d3415d30c9513557ad3d003fc243d332489..814b5b035a6b96376997c1dd8a2d257590f68fb2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx @@ -2,7 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import styles from '../styles.scss'; -const MESSAGE_INTERVAL = 50; +const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations; +const MESSAGE_FREQUENCY = ANNOTATION_CONFIG.message_frequency; +const DRAW_START = ANNOTATION_CONFIG.status.start; +const DRAW_UPDATE = ANNOTATION_CONFIG.status.update; +const DRAW_END = ANNOTATION_CONFIG.status.end; export default class PencilDrawListener extends Component { constructor() { @@ -55,10 +59,10 @@ export default class PencilDrawListener extends Component { // sending the first message const _points = [transformedSvgPoint.x, transformedSvgPoint.y]; - this.handleDrawPencil(_points, 'DRAW_START', generateNewShapeId()); + this.handleDrawPencil(_points, DRAW_START, generateNewShapeId()); // All the DRAW_UPDATE messages will be send on timer by sendCoordinates func - this.intervalId = setInterval(this.sendCoordinates, MESSAGE_INTERVAL); + this.intervalId = setInterval(this.sendCoordinates, MESSAGE_FREQUENCY); // if you switch to a different window using Alt+Tab while mouse is down and release it // it wont catch mouseUp and will keep tracking the movements. Thus we need this check. @@ -99,7 +103,7 @@ export default class PencilDrawListener extends Component { sendCoordinates() { if (this.isDrawing && this.points.length > 0) { const { getCurrentShapeId } = this.props.actions; - this.handleDrawPencil(this.points, 'DRAW_UPDATE', getCurrentShapeId()); + this.handleDrawPencil(this.points, DRAW_UPDATE, getCurrentShapeId()); this.points = []; } } @@ -145,7 +149,7 @@ export default class PencilDrawListener extends Component { this.handleDrawPencil( this.points, - 'DRAW_END', + DRAW_END, getCurrentShapeId(), [Math.round(physicalSlideWidth), Math.round(physicalSlideHeight)], ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js index 17248925a974f41b1ec1f3f38deae2f88af7dbb0..122f1b6c3a7640417c5e5bd70a46a806cc72d8d1 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js @@ -2,47 +2,50 @@ import { makeCall } from '/imports/ui/services/api'; import Storage from '/imports/ui/services/storage/session'; import Auth from '/imports/ui/services/auth'; +const DRAW_SETTINGS = 'drawSettings'; + const sendAnnotation = (annotation) => { makeCall('sendAnnotation', annotation); }; const getWhiteboardToolbarValues = () => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - const { - whiteboardAnnotationTool, - whiteboardAnnotationThickness, - whiteboardAnnotationColor, - textFontSize, - textShape, - } = drawSettings; - - return { - tool: whiteboardAnnotationTool, - thickness: whiteboardAnnotationThickness, - color: whiteboardAnnotationColor, - textFontSize, - textShapeValue: textShape.textShapeValue ? textShape.textShapeValue : '', - textShapeActiveId: textShape.textShapeActiveId ? textShape.textShapeActiveId : '', - }; + const drawSettings = Storage.getItem(DRAW_SETTINGS); + if (!drawSettings) { + return {}; } - return {}; + + const { + whiteboardAnnotationTool, + whiteboardAnnotationThickness, + whiteboardAnnotationColor, + textFontSize, + textShape, + } = drawSettings; + + return { + tool: whiteboardAnnotationTool, + thickness: whiteboardAnnotationThickness, + color: whiteboardAnnotationColor, + textFontSize, + textShapeValue: textShape.textShapeValue ? textShape.textShapeValue : '', + textShapeActiveId: textShape.textShapeActiveId ? textShape.textShapeActiveId : '', + }; }; const resetTextShapeSession = () => { - const drawSettings = Storage.getItem('drawSettings'); + const drawSettings = Storage.getItem(DRAW_SETTINGS); if (drawSettings) { drawSettings.textShape.textShapeValue = ''; drawSettings.textShape.textShapeActiveId = ''; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); + Storage.setItem(DRAW_SETTINGS, drawSettings); } }; const setTextShapeActiveId = (id) => { - const drawSettings = Storage.getItem('drawSettings'); + const drawSettings = Storage.getItem(DRAW_SETTINGS); if (drawSettings) { drawSettings.textShape.textShapeActiveId = id; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); + Storage.setItem(DRAW_SETTINGS, drawSettings); } }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx index f80c6e3b3a3be4ab98c6caa5beac0179f360c34f..fd8ceaa8b93539c42316cbc18274506c1827af21 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx @@ -2,12 +2,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import styles from '../styles.scss'; -const MESSAGE_INTERVAL = 50; +const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations; +const MESSAGE_FREQUENCY = ANNOTATION_CONFIG.message_frequency; +const DRAW_START = ANNOTATION_CONFIG.status.start; +const DRAW_UPDATE = ANNOTATION_CONFIG.status.update; +const DRAW_END = ANNOTATION_CONFIG.status.end; export default class ShapeDrawListener extends Component { constructor(props) { super(props); + // there is no valid defaults for the coordinates, and we wouldn't want them anyway this.initialCoordinate = { x: undefined, y: undefined, @@ -29,9 +34,9 @@ export default class ShapeDrawListener extends Component { // id of the setInterval() this.intervalId = 0; - this.mouseDownHandler = this.mouseDownHandler.bind(this); - this.mouseMoveHandler = this.mouseMoveHandler.bind(this); - this.mouseUpHandler = this.mouseUpHandler.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); this.resetState = this.resetState.bind(this); this.sendLastMessage = this.sendLastMessage.bind(this); this.sendCoordinates = this.sendCoordinates.bind(this); @@ -50,15 +55,15 @@ export default class ShapeDrawListener extends Component { } // main mouse down handler - mouseDownHandler(event) { + handleMouseDown(event) { // Sometimes when you Alt+Tab while drawing it can happen that your mouse is up, // but the browser didn't catch it. So check it here. if (this.isDrawing) { return this.sendLastMessage(); } - window.addEventListener('mouseup', this.mouseUpHandler); - window.addEventListener('mousemove', this.mouseMoveHandler, true); + window.addEventListener('mouseup', this.handleMouseUp); + window.addEventListener('mousemove', this.handleMouseMove, true); this.isDrawing = true; const { @@ -77,7 +82,7 @@ export default class ShapeDrawListener extends Component { generateNewShapeId(); // setting the initial current status - this.currentStatus = 'DRAW_START'; + this.currentStatus = DRAW_START; // saving the coordinates for future references this.initialCoordinate = { @@ -91,65 +96,71 @@ export default class ShapeDrawListener extends Component { }; // All the messages will be send on timer by sendCoordinates func - this.intervalId = setInterval(this.sendCoordinates, MESSAGE_INTERVAL); + this.intervalId = setInterval(this.sendCoordinates, MESSAGE_FREQUENCY); return true; } // main mouse move handler - // calls a mouseMove<AnnotationName> handler based on the tool selected - mouseMoveHandler(event) { - if (this.isDrawing) { - const { - checkIfOutOfBounds, - getTransformedSvgPoint, - svgCoordinateToPercentages, - } = this.props.actions; + handleMouseMove(event) { + if (!this.isDrawing) { + return; + } - // get the transformed svg coordinate - let transformedSvgPoint = getTransformedSvgPoint(event); + const { + checkIfOutOfBounds, + getTransformedSvgPoint, + svgCoordinateToPercentages, + } = this.props.actions; - // check if it's out of bounds - transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint); + // get the transformed svg coordinate + let transformedSvgPoint = getTransformedSvgPoint(event); - // transforming svg coordinate to percentages relative to the slide width/height - transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint); + // check if it's out of bounds + transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint); - // saving the last sent coordinate - this.currentCoordinate = transformedSvgPoint; - } + // transforming svg coordinate to percentages relative to the slide width/height + transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint); + + // saving the last sent coordinate + this.currentCoordinate = transformedSvgPoint; } // main mouse up handler - mouseUpHandler() { + handleMouseUp() { this.sendLastMessage(); } sendCoordinates() { // check the current drawing status - if (this.isDrawing) { - // check if a current coordinate is not the same as an initial one - // it prevents us from drawing dots on random clicks - if (this.currentCoordinate.x !== this.initialCoordinate.x || - this.currentCoordinate.y !== this.initialCoordinate.y) { - // check if previously sent coordinate is not equal to a current one - if (this.currentCoordinate.x !== this.lastSentCoordinate.x || - this.currentCoordinate.y !== this.lastSentCoordinate.y) { - const { getCurrentShapeId } = this.props.actions; - this.handleDrawCommonAnnotation( - this.initialCoordinate, - this.currentCoordinate, - this.currentStatus, - getCurrentShapeId(), - this.props.drawSettings.tool, - ); - this.lastSentCoordinate = this.currentCoordinate; - - if (this.currentStatus === 'DRAW_START') { - this.currentStatus = 'DRAW_UPDATE'; - } - } - } + if (!this.isDrawing) { + return; + } + // check if a current coordinate is not the same as an initial one + // it prevents us from drawing dots on random clicks + if (this.currentCoordinate.x === this.initialCoordinate.x && + this.currentCoordinate.y === this.initialCoordinate.y) { + return; + } + + // check if previously sent coordinate is not equal to a current one + if (this.currentCoordinate.x === this.lastSentCoordinate.x && + this.currentCoordinate.y === this.lastSentCoordinate.y) { + return; + } + + const { getCurrentShapeId } = this.props.actions; + this.handleDrawCommonAnnotation( + this.initialCoordinate, + this.currentCoordinate, + this.currentStatus, + getCurrentShapeId(), + this.props.drawSettings.tool, + ); + this.lastSentCoordinate = this.currentCoordinate; + + if (this.currentStatus === DRAW_START) { + this.currentStatus = DRAW_UPDATE; } } @@ -160,19 +171,25 @@ export default class ShapeDrawListener extends Component { if (this.isDrawing) { // make sure we are drawing and we have some coordinates sent for this shape before - // to prevent sending 'DRAW_END on a random mouse click + // to prevent sending DRAW_END on a random mouse click if (this.lastSentCoordinate.x && this.lastSentCoordinate.y) { const { getCurrentShapeId } = this.props.actions; - this.handleDrawCommonAnnotation(this.initialCoordinate, this.currentCoordinate, 'DRAW_END', getCurrentShapeId(), this.props.drawSettings.tool); + this.handleDrawCommonAnnotation( + this.initialCoordinate, + this.currentCoordinate, + DRAW_END, + getCurrentShapeId(), + this.props.drawSettings.tool, + ); } this.resetState(); } } resetState() { - // resetting the current state - window.removeEventListener('mouseup', this.mouseUpHandler); - window.removeEventListener('mousemove', this.mouseMoveHandler, true); + // resetting the current drawing state + window.removeEventListener('mouseup', this.handleMouseUp); + window.removeEventListener('mousemove', this.handleMouseMove, true); this.isDrawing = false; this.currentStatus = undefined; this.initialCoordinate = { @@ -227,7 +244,7 @@ export default class ShapeDrawListener extends Component { role="presentation" className={styles[tool]} style={{ width: '100%', height: '100%' }} - onMouseDown={this.mouseDownHandler} + onMouseDown={this.handleMouseDown} /> ); } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx index eb4bcb4280748dbd89a851c8e6d3595bbc55a693..bf29ccac2d77482291fe83e5b48af38ac65fc2b2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx @@ -2,6 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import styles from '../styles.scss'; +const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations; +const DRAW_START = ANNOTATION_CONFIG.status.start; +const DRAW_UPDATE = ANNOTATION_CONFIG.status.update; +const DRAW_END = ANNOTATION_CONFIG.status.end; + export default class TextDrawListener extends Component { constructor() { super(); @@ -31,12 +36,12 @@ export default class TextDrawListener extends Component { this.currentWidth = undefined; this.currentHeight = undefined; - // current text shape status, it may change between "DRAW_START", "DRAW_UPDATE", "DRAW_END" + // current text shape status, it may change between DRAW_START, DRAW_UPDATE, DRAW_END this.currentStatus = ''; - this.mouseDownHandler = this.mouseDownHandler.bind(this); - this.mouseMoveHandler = this.mouseMoveHandler.bind(this); - this.mouseUpHandler = this.mouseUpHandler.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); this.resetState = this.resetState.bind(this); this.sendLastMessage = this.sendLastMessage.bind(this); } @@ -50,27 +55,29 @@ export default class TextDrawListener extends Component { // While the user was drawing it. So we are resetting the state. componentWillReceiveProps(nextProps) { const { drawSettings } = this.props; - const _drawsettings = nextProps.drawSettings; + const nextDrawsettings = nextProps.drawSettings; - if (drawSettings.textShapeActiveId !== '' && _drawsettings.textShapeActiveId === '') { + if (drawSettings.textShapeActiveId !== '' && nextDrawsettings.textShapeActiveId === '') { this.resetState(); } } componentDidUpdate(prevProps) { const { drawSettings } = this.props; - const _drawsettings = prevProps.drawSettings; - const _textShapeValue = prevProps.drawSettings.textShapeValue; + const prevDrawsettings = prevProps.drawSettings; + const prevTextShapeValue = prevProps.drawSettings.textShapeValue; // Updating the component in cases when: // Either color / font-size or text value has changed // and excluding the case when the textShapeActiveId changed to '' - if ((drawSettings.textFontSize !== _drawsettings.textFontSize || - drawSettings.color !== _drawsettings.color || - drawSettings.textShapeValue !== _textShapeValue) && - drawSettings.textShapeActiveId !== '') { + const fontSizeChanged = drawSettings.textFontSize !== prevDrawsettings.textFontSize; + const colorChanged = drawSettings.color !== prevDrawsettings.color; + const textShapeValueChanged = drawSettings.textShapeValue !== prevTextShapeValue; + const textShapeIdNotEmpty = drawSettings.textShapeActiveId !== ''; + + if ((fontSizeChanged || colorChanged || textShapeValueChanged) && textShapeIdNotEmpty) { const { getCurrentShapeId } = this.props.actions; - this.currentStatus = 'DRAW_UPDATE'; + this.currentStatus = DRAW_UPDATE; this.handleDrawText( { x: this.currentX, y: this.currentY }, @@ -85,8 +92,8 @@ export default class TextDrawListener extends Component { componentWillUnmount() { window.removeEventListener('beforeunload', this.sendLastMessage); - window.removeEventListener('mouseup', this.mouseUpHandler); - window.removeEventListener('mousemove', this.mouseMoveHandler, true); + window.removeEventListener('mouseup', this.handleMouseUp); + window.removeEventListener('mousemove', this.handleMouseMove, true); // sending the last message on componentDidUnmount // for example in case when you switched a tool while drawing text shape @@ -94,30 +101,27 @@ export default class TextDrawListener extends Component { } // main mouse down handler - // calls a mouseDown<AnnotationName> handler based on the tool selected - mouseDownHandler(event) { + handleMouseDown(event) { this.mouseDownText(event); } // main mouse up handler - // calls a mouseUp<AnnotationName> handler based on the tool selected - mouseUpHandler(event) { - window.removeEventListener('mouseup', this.mouseUpHandler); - window.removeEventListener('mousemove', this.mouseMoveHandler, true); + handleMouseUp(event) { + window.removeEventListener('mouseup', this.handleMouseUp); + window.removeEventListener('mousemove', this.handleMouseMove, true); this.mouseUpText(event); } // main mouse move handler - // calls a mouseMove<AnnotationName> handler based on the tool selected - mouseMoveHandler(event) { + handleMouseMove(event) { this.mouseMoveText(event); } mouseDownText(event) { // if our current drawing state is not drawing the box and not writing the text if (!this.state.isDrawing && !this.state.isWritingText) { - window.addEventListener('mouseup', this.mouseUpHandler); - window.addEventListener('mousemove', this.mouseMoveHandler, true); + window.addEventListener('mouseup', this.handleMouseUp); + window.addEventListener('mousemove', this.handleMouseMove, true); // saving initial X and Y coordinates for further displaying of the textarea this.initialX = event.nativeEvent.offsetX; @@ -137,21 +141,23 @@ export default class TextDrawListener extends Component { } sendLastMessage() { - if (this.state.isWritingText) { - const { getCurrentShapeId } = this.props.actions; - this.currentStatus = 'DRAW_END'; + if (!this.state.isWritingText) { + return; + } - this.handleDrawText( - { x: this.currentX, y: this.currentY }, - this.currentWidth, - this.currentHeight, - this.currentStatus, - getCurrentShapeId(), - this.props.drawSettings.textShapeValue, - ); + const { getCurrentShapeId } = this.props.actions; + this.currentStatus = DRAW_END; - this.resetState(); - } + this.handleDrawText( + { x: this.currentX, y: this.currentY }, + this.currentWidth, + this.currentHeight, + this.currentStatus, + getCurrentShapeId(), + this.props.drawSettings.textShapeValue, + ); + + this.resetState(); } resetState() { @@ -206,39 +212,41 @@ export default class TextDrawListener extends Component { mouseUpText() { // TODO - find if the size is large enough to display the text area - if (this.state.isDrawing && !this.state.isWritingText) { - const { generateNewShapeId, - getCurrentShapeId, - setTextShapeActiveId, - } = this.props.actions; - - // coordinates and width/height of the textarea in percentages of the current slide - // saving them in the class since they will be used during all updates - this.currentX = (this.state.textBoxX / this.props.slideWidth) * 100; - this.currentY = (this.state.textBoxY / this.props.slideHeight) * 100; - this.currentWidth = (this.state.textBoxWidth / this.props.slideWidth) * 100; - this.currentHeight = (this.state.textBoxHeight / this.props.slideHeight) * 100; - this.currentStatus = 'DRAW_START'; - this.handleDrawText( - { x: this.currentX, y: this.currentY }, - this.currentWidth, - this.currentHeight, - this.currentStatus, - generateNewShapeId(), - '', - ); + if (!this.state.isDrawing && this.state.isWritingText) { + return; + } - setTextShapeActiveId(getCurrentShapeId()); + const { generateNewShapeId, + getCurrentShapeId, + setTextShapeActiveId, + } = this.props.actions; + + // coordinates and width/height of the textarea in percentages of the current slide + // saving them in the class since they will be used during all updates + this.currentX = (this.state.textBoxX / this.props.slideWidth) * 100; + this.currentY = (this.state.textBoxY / this.props.slideHeight) * 100; + this.currentWidth = (this.state.textBoxWidth / this.props.slideWidth) * 100; + this.currentHeight = (this.state.textBoxHeight / this.props.slideHeight) * 100; + this.currentStatus = DRAW_START; + this.handleDrawText( + { x: this.currentX, y: this.currentY }, + this.currentWidth, + this.currentHeight, + this.currentStatus, + generateNewShapeId(), + '', + ); - this.setState({ - isWritingText: true, - isDrawing: false, - textBoxX: undefined, - textBoxY: undefined, - textBoxWidth: 0, - textBoxHeight: 0, - }); - } + setTextShapeActiveId(getCurrentShapeId()); + + this.setState({ + isWritingText: true, + isDrawing: false, + textBoxX: undefined, + textBoxY: undefined, + textBoxWidth: 0, + textBoxHeight: 0, + }); } handleDrawText(startPoint, width, height, status, id, text) { @@ -277,7 +285,7 @@ export default class TextDrawListener extends Component { role="presentation" className={styles.text} style={{ width: '100%', height: '100%' }} - onMouseDown={this.mouseDownHandler} + onMouseDown={this.handleMouseDown} > {this.state.isDrawing ? <svg @@ -334,7 +342,7 @@ TextDrawListener.propTypes = { // Defines a function which generates a new shape id generateNewShapeId: PropTypes.func.isRequired, // Defines a function which receives a thickness num and normalizes it before we send a message - normalizeThickness: PropTypes.func.isRequired, + normalizeFont: PropTypes.func.isRequired, // Defines a function which we use to publish a message to the server sendAnnotation: PropTypes.func.isRequired, // Defines a function which resets the current state of the text shape drawing diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx index 7d91bf70c01e0c2db3589749d6165d3e1d54d07b..d9fb758a6831c8503a912a2be7824d930ef6b673 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx @@ -1,29 +1,23 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt'; +import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import styles from './styles.scss'; -import WhiteboardToolbarItem from './whiteboard-toolbar-item/component'; +import ToolbarMenuItem from './toolbar-menu-item/component'; +import ToolbarSubmenu from './toolbar-submenu/component'; -export default class WhiteboardToolbar extends Component { +const TRANSITION_DURATION = '0.4s'; +const TOOLBAR_CONFIG = Meteor.settings.public.whiteboard.toolbar; +const ANNOTATION_COLORS = TOOLBAR_CONFIG.colors; +const THICKNESS_RADIUSES = TOOLBAR_CONFIG.thickness; +const FONT_SIZES = TOOLBAR_CONFIG.font_sizes; +const ANNOTATION_TOOLS = TOOLBAR_CONFIG.tools; - static HEXToINTColor(hexColor) { - const _rrggbb = hexColor.slice(1); - const rrggbb = _rrggbb.substr(0, 2) + _rrggbb.substr(2, 2) + _rrggbb.substr(4, 2); - return parseInt(rrggbb, 16); - } - - static INTToHEXColor(intColor) { - let hex; - hex = parseInt(intColor, 10).toString(16); - while (hex.length < 6) { - hex = `0${hex}`; - } +class WhiteboardToolbar extends Component { - return `#${hex}`; - } - - constructor(props) { - super(props); + constructor() { + super(); this.state = { // a variable to control which list is currently open @@ -32,15 +26,15 @@ export default class WhiteboardToolbar extends Component { // variables to keep current selected draw settings annotationSelected: { icon: 'hand', - sessionValue: 'hand', + value: 'hand', }, - thicknessSelected: 4, - colorSelected: '#000000', - fontSizeSelected: 20, + thicknessSelected: { value: 4 }, + colorSelected: { value: '#000000' }, + fontSizeSelected: { value: 20 }, // keeping the previous color and the thickness icon's radius selected for svg animation - prevColorSelected: '#000000', - prevIconRadius: 4, + prevColorSelected: { value: '#000000' }, + prevThicknessSelected: { value: 4 }, // lists of tools/thickness/colors are not direct children of main toolbar buttons // and we want the list to close when onBlur fires at the main toolbar button @@ -58,12 +52,8 @@ export default class WhiteboardToolbar extends Component { this.handleThicknessChange = this.handleThicknessChange.bind(this); this.handleFontSizeChange = this.handleFontSizeChange.bind(this); this.handleColorChange = this.handleColorChange.bind(this); - this.disableOnBlur = this.disableOnBlur.bind(this); - this.enableOnBlur = this.enableOnBlur.bind(this); - this.renderAnnotationList = this.renderAnnotationList.bind(this); - this.renderFontSizeList = this.renderFontSizeList.bind(this); - this.renderThicknessList = this.renderThicknessList.bind(this); - this.renderColorList = this.renderColorList.bind(this); + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); } componentWillMount() { @@ -77,10 +67,10 @@ export default class WhiteboardToolbar extends Component { // setting default drawing settings if they haven't been set previously const { annotationSelected, thicknessSelected, colorSelected, fontSizeSelected } = this.state; this.props.actions.setInitialWhiteboardToolbarValues( - annotationSelected.sessionValue, - thicknessSelected, - WhiteboardToolbar.HEXToINTColor(colorSelected), - fontSizeSelected, + annotationSelected.value, + thicknessSelected.value * 2, + HEXToINTColor(colorSelected.value), + fontSizeSelected.value, { textShapeValue: '', textShapeActiveId: '', @@ -90,10 +80,7 @@ export default class WhiteboardToolbar extends Component { } componentDidMount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - - if (this.state.annotationSelected.sessionValue !== 'text') { + if (this.state.annotationSelected.value !== 'text') { // trigger initial animation on the thickness circle, otherwise it stays at 0 this.thicknessListIconColor.beginElement(); this.thicknessListIconRadius.beginElement(); @@ -109,20 +96,15 @@ export default class WhiteboardToolbar extends Component { this.animateSvgIcons(prevState); } - componentWillUnmount() { - // to let the whiteboard know that the presentation area's size has changed - window.dispatchEvent(new Event('resize')); - } - setToolbarValues(drawSettings) { // divide by 2, since we need the radius for the thickness icon - const thicknessSelected = drawSettings.whiteboardAnnotationThickness / 2; - const fontSizeSelected = drawSettings.textFontSize; - const colorSelected = WhiteboardToolbar.INTToHEXColor(drawSettings.whiteboardAnnotationColor); + const thicknessSelected = { value: drawSettings.whiteboardAnnotationThickness / 2 }; + const fontSizeSelected = { value: drawSettings.textFontSize }; + const colorSelected = { value: INTToHEXColor(drawSettings.whiteboardAnnotationColor) }; let annotationSelected = {}; for (let i = 0; i < this.props.annotations.length; i += 1) { - if (drawSettings.whiteboardAnnotationTool === this.props.annotations[i].sessionValue) { + if (drawSettings.whiteboardAnnotationTool === this.props.annotations[i].value) { annotationSelected = this.props.annotations[i]; break; } @@ -149,19 +131,19 @@ export default class WhiteboardToolbar extends Component { */ // 1st case - if (this.state.colorSelected !== prevState.colorSelected) { + if (this.state.colorSelected.value !== prevState.colorSelected.value) { // 1st case b) - if (this.state.annotationSelected.sessionValue !== 'text') { + if (this.state.annotationSelected.value !== 'text') { this.thicknessListIconColor.beginElement(); } // 1st case a) this.colorListIconColor.beginElement(); // 2nd case - } else if (this.state.thicknessSelected !== prevState.thicknessSelected) { + } else if (this.state.thicknessSelected.value !== prevState.thicknessSelected.value) { this.thicknessListIconRadius.beginElement(); // 3rd case - } else if (this.state.annotationSelected.sessionValue !== 'text' && - prevState.annotationSelected.sessionValue === 'text') { + } else if (this.state.annotationSelected.value !== 'text' && + prevState.annotationSelected.value === 'text') { this.thicknessListIconRadius.beginElement(); this.thicknessListIconColor.beginElement(); } @@ -179,7 +161,7 @@ export default class WhiteboardToolbar extends Component { // close a current submenu (fires onBlur only, when you click anywhere on the screen) closeSubMenu() { // a separate case for the active text shape - if (this.state.annotationSelected.sessionValue === 'text' && this.props.textShapeActiveId !== '') { + if (this.state.annotationSelected.value === 'text' && this.props.textShapeActiveId !== '') { return; } @@ -214,33 +196,33 @@ export default class WhiteboardToolbar extends Component { }; // to animate thickness icon properly when you switch the tool back from Text - if (annotation.sessionValue === 'text') { - obj.prevIconRadius = 0; + if (annotation.value === 'text') { + obj.prevThicknessSelected = { value: 0 }; } - this.props.actions.setTool(annotation.sessionValue); + this.props.actions.setTool(annotation.value); this.setState(obj); } // changes a current selected thickness both in the state and in the session // and closes the thickness list - handleThicknessChange(thicknessValue) { - // thicknessValue * 2 since this is radius, we need to double it - this.props.actions.setThickness(thicknessValue * 2); + handleThicknessChange(thicknessSelected) { + // thickness value * 2 since this is radius, we need to double it + this.props.actions.setThickness(thicknessSelected.value * 2); this.setState({ - prevIconRadius: this.state.thicknessSelected, - thicknessSelected: thicknessValue, + prevThicknessSelected: this.state.thicknessSelected, + thicknessSelected, onBlurEnabled: true, currentSubmenuOpen: '', }); } - handleFontSizeChange(fontSizeValue) { - this.props.actions.setFontSize(fontSizeValue); + handleFontSizeChange(fontSize) { + this.props.actions.setFontSize(fontSize.value); this.setState({ - fontSizeSelected: fontSizeValue, + fontSizeSelected: fontSize, onBlurEnabled: true, currentSubmenuOpen: '', }); @@ -249,7 +231,7 @@ export default class WhiteboardToolbar extends Component { // changes a current selected color both in the state and in the session // and closes the color list handleColorChange(color) { - this.props.actions.setColor(WhiteboardToolbar.HEXToINTColor(color)); + this.props.actions.setColor(HEXToINTColor(color.value)); this.setState({ prevColorSelected: this.state.colorSelected, @@ -260,245 +242,249 @@ export default class WhiteboardToolbar extends Component { } // disabling onBlur flag when mouse is over the items in the lists - disableOnBlur() { + handleMouseEnter() { this.setState({ onBlurEnabled: false, }); } // enabling the onBlur flag when the mouse leaving the lists - enableOnBlur() { + handleMouseLeave() { this.setState({ onBlurEnabled: true, }); } - renderAnnotationList() { - const { annotations } = this.props; + renderToolItem() { + return ( + <ToolbarMenuItem + label={'Tools'} + icon={this.state.annotationSelected.icon} + onItemClick={this.displaySubMenu} + objectToReturn={'annotationList'} + onBlur={this.closeSubMenu} + className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? '' : styles.notActive)} + > + {this.state.currentSubmenuOpen === 'annotationList' ? + <ToolbarSubmenu + type="annotations" + customIcon={false} + label="Annotations" + onItemClick={this.handleAnnotationChange} + objectsToRender={this.props.annotations} + objectSelected={this.state.annotationSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + : null } + </ToolbarMenuItem> + ); + } + renderFontItem() { return ( - <div className={cx(styles.annotationList, styles.toolbarList)}> - { annotations ? annotations.map(annotation => - ( - <WhiteboardToolbarItem - label={'Annotation'} - icon={annotation.icon} - onItemClick={this.handleAnnotationChange} - objectToReturn={annotation} - className={cx(styles.toolbarListButton, this.state.annotationSelected.sessionValue === annotation.sessionValue ? styles.selectedListButton : '')} - onMouseEnter={this.disableOnBlur} - onMouseLeave={this.enableOnBlur} - key={annotation.sessionValue} - /> - ), - ) : null} - </div> + <ToolbarMenuItem + label={'Font Size List'} + customIcon={this.renderFontItemIcon()} + onItemClick={this.displaySubMenu} + objectToReturn={'fontSizeList'} + onBlur={this.closeSubMenu} + className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? '' : styles.notActive)} + > + {this.state.currentSubmenuOpen === 'fontSizeList' ? + <ToolbarSubmenu + type="font-size" + customIcon + label="Font Size" + onItemClick={this.handleFontSizeChange} + objectsToRender={this.props.fontSizes} + objectSelected={this.state.fontSizeSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + : null } + </ToolbarMenuItem> ); } - renderFontSizeList() { - const { fontSizes } = this.props; + renderFontItemIcon() { + return ( + <p + className={styles.textThickness} + style={{ + fontSize: this.state.fontSizeSelected.value, + color: this.state.colorSelected.value, + WebkitTransition: `color ${TRANSITION_DURATION}, font-size ${TRANSITION_DURATION}`, /* Safari */ + transition: `color ${TRANSITION_DURATION}, font-size ${TRANSITION_DURATION}`, + }} + > + Aa + </p> + ); + } + renderThicknessItem() { return ( - <div className={cx(styles.fontSizeList, styles.toolbarList)}> - {fontSizes ? fontSizes.map(fontSizeValue => - ( - <WhiteboardToolbarItem - label={'Font Size'} - customIcon={ - <p className={styles.textThickness} style={{ fontSize: fontSizeValue }}> - Aa - </p> - } - onItemClick={this.handleFontSizeChange} - objectToReturn={fontSizeValue} - className={cx(styles.toolbarListButton, styles.fontSizeListButton, this.state.fontSizeSelected === fontSizeValue ? styles.selectedListButton : '')} - onMouseEnter={this.disableOnBlur} - onMouseLeave={this.enableOnBlur} - key={fontSizeValue} - /> - ), - ) : null} - </div> + <ToolbarMenuItem + label={'Thickness List'} + onItemClick={this.displaySubMenu} + objectToReturn={'thicknessList'} + onBlur={this.closeSubMenu} + className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? '' : styles.notActive)} + customIcon={this.renderThicknessItemIcon()} + > + {this.state.currentSubmenuOpen === 'thicknessList' ? + <ToolbarSubmenu + type="thickness" + customIcon + label="Thickness" + onItemClick={this.handleThicknessChange} + objectsToRender={this.props.thicknessRadiuses} + objectSelected={this.state.thicknessSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + : null } + </ToolbarMenuItem> ); } - renderThicknessList() { - const { thicknessRadiuses } = this.props; + renderThicknessItemIcon() { + return ( + <svg className={styles.customSvgIcon} shapeRendering="geometricPrecision"> + <circle + shapeRendering="geometricPrecision" + cx="50%" + cy="50%" + stroke="black" + strokeWidth="1" + > + <animate + ref={(ref) => { this.thicknessListIconColor = ref; }} + attributeName="fill" + attributeType="XML" + from={this.state.prevColorSelected.value} + to={this.state.colorSelected.value} + begin={'indefinite'} + dur={TRANSITION_DURATION} + repeatCount="0" + fill="freeze" + /> + <animate + ref={(ref) => { this.thicknessListIconRadius = ref; }} + attributeName="r" + attributeType="XML" + from={this.state.prevThicknessSelected.value} + to={this.state.thicknessSelected.value} + begin={'indefinite'} + dur={TRANSITION_DURATION} + repeatCount="0" + fill="freeze" + /> + </circle> + </svg> + ); + } + renderColorItem() { return ( - <div className={cx(styles.thicknessList, styles.toolbarList)}> - {thicknessRadiuses ? thicknessRadiuses.map(thicknessRadius => - ( - <WhiteboardToolbarItem - label={'Radius'} - customIcon={ - <svg className={styles.customSvgIcon}> - <circle cx="50%" cy="50%" r={thicknessRadius} fill="#F3F6F9" /> - </svg> - } - onItemClick={this.handleThicknessChange} - objectToReturn={thicknessRadius} - className={cx(styles.toolbarListButton, this.state.thicknessSelected === thicknessRadius ? styles.selectedListButton : '')} - onMouseEnter={this.disableOnBlur} - onMouseLeave={this.enableOnBlur} - key={thicknessRadius} - /> - ), - ) : null} - </div> + <ToolbarMenuItem + label={'Color List'} + onItemClick={this.displaySubMenu} + objectToReturn={'colorList'} + onBlur={this.closeSubMenu} + className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? '' : styles.notActive)} + customIcon={this.renderColorItemIcon()} + > + {this.state.currentSubmenuOpen === 'colorList' ? + <ToolbarSubmenu + type="color" + customIcon + label="Color" + onItemClick={this.handleColorChange} + objectsToRender={this.props.colors} + objectSelected={this.state.colorSelected} + handleMouseEnter={this.handleMouseEnter} + handleMouseLeave={this.handleMouseLeave} + /> + : null } + </ToolbarMenuItem> + ); + } + + renderColorItemIcon() { + return ( + <svg className={styles.customSvgIcon}> + <rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1"> + <animate + ref={(ref) => { this.colorListIconColor = ref; }} + attributeName="fill" + attributeType="XML" + from={this.state.prevColorSelected.value} + to={this.state.colorSelected.value} + begin={'indefinite'} + dur={TRANSITION_DURATION} + repeatCount="0" + fill="freeze" + /> + </rect> + </svg> ); } - renderColorList() { - const { colors } = this.props; + renderUndoItem() { + return ( + <ToolbarMenuItem + label={'Undo Annotation'} + icon={'undo'} + onItemClick={this.handleUndo} + className={cx(styles.toolbarButton, styles.notActive)} + /> + ); + } + renderClearAllItem() { return ( - <div className={cx(styles.colorList, styles.toolbarList)}> - {colors ? colors.map(color => - ( - <WhiteboardToolbarItem - label={'Color'} - customIcon={ - <svg className={styles.customSvgIcon}> - <rect x="20%" y="20%" width="60%" height="60%" fill={color} /> - </svg> - } - onItemClick={this.handleColorChange} - objectToReturn={color} - className={cx(styles.toolbarListButton, this.state.colorSelected === color ? styles.selectedListButton : '')} - onMouseEnter={this.disableOnBlur} - onMouseLeave={this.enableOnBlur} - key={color} - /> - ), - ) : null} - </div> + <ToolbarMenuItem + label={'Clear All Annotations'} + icon={'circle_close'} + onItemClick={this.handleClearAll} + className={cx(styles.toolbarButton, styles.notActive)} + /> + ); + } + + renderMultiUserItem() { + const { multiUser } = this.props; + + return ( + <ToolbarMenuItem + label={multiUser ? 'Turn multi-user mode off' : 'Tuen multi-user mode on'} + icon={multiUser ? 'multi_whiteboard' : 'whiteboard'} + onItemClick={this.handleSwitchWhiteboardMode} + className={cx(styles.toolbarButton, styles.notActive)} + /> ); } render() { + const { annotationSelected } = this.state; + const { isPresenter } = this.props; return ( <div className={styles.toolbarContainer} style={{ height: this.props.height }}> <div className={styles.toolbarWrapper}> - <WhiteboardToolbarItem - label={'Tools'} - icon={this.state.annotationSelected.icon} - onItemClick={this.displaySubMenu} - objectToReturn={'annotationList'} - onBlur={this.closeSubMenu} - className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? '' : styles.notActive)} - renderSubMenu={this.state.currentSubmenuOpen === 'annotationList' ? this.renderAnnotationList : null} - /> - {this.state.annotationSelected.sessionValue === 'text' ? - <WhiteboardToolbarItem - label={'Font Size List'} - customIcon={ - <p - className={styles.textThickness} - style={{ - fontSize: this.state.fontSizeSelected, - color: this.state.colorSelected, - }} - > - Aa - </p> - } - onItemClick={this.displaySubMenu} - objectToReturn={'fontSizeList'} - onBlur={this.closeSubMenu} - className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? '' : styles.notActive)} - renderSubMenu={this.state.currentSubmenuOpen === 'fontSizeList' ? this.renderFontSizeList : null} - /> + {this.renderToolItem()} + {annotationSelected.value === 'text' ? + this.renderFontItem() : - <WhiteboardToolbarItem - label={'Thickness List'} - onItemClick={this.displaySubMenu} - objectToReturn={'thicknessList'} - onBlur={this.closeSubMenu} - className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? '' : styles.notActive)} - renderSubMenu={this.state.currentSubmenuOpen === 'thicknessList' ? this.renderThicknessList : null} - customIcon={ - <svg className={styles.customSvgIcon} shapeRendering="geometricPrecision"> - <circle - shapeRendering="geometricPrecision" - cx="50%" - cy="50%" - stroke="black" - strokeWidth="1" - > - <animate - ref={(ref) => { this.thicknessListIconColor = ref; }} - attributeName="fill" - attributeType="XML" - from={this.state.prevColorSelected} - to={this.state.colorSelected} - begin={'indefinite'} - dur="0.4s" - repeatCount="0" - fill="freeze" - /> - <animate - ref={(ref) => { this.thicknessListIconRadius = ref; }} - attributeName="r" - attributeType="XML" - from={this.state.prevIconRadius} - to={this.state.thicknessSelected} - begin={'indefinite'} - dur="0.4s" - repeatCount="0" - fill="freeze" - /> - </circle> - </svg> - } - /> - } - <WhiteboardToolbarItem - label={'Color List'} - onItemClick={this.displaySubMenu} - objectToReturn={'colorList'} - onBlur={this.closeSubMenu} - className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? '' : styles.notActive)} - renderSubMenu={this.state.currentSubmenuOpen === 'colorList' ? this.renderColorList : null} - customIcon={ - <svg className={styles.customSvgIcon}> - <rect x="25%" y="25%" width="50%" height="50%" stroke="black" strokeWidth="1"> - <animate - ref={(ref) => { this.colorListIconColor = ref; }} - attributeName="fill" - attributeType="XML" - from={this.state.prevColorSelected} - to={this.state.colorSelected} - begin={'indefinite'} - dur="0.4s" - repeatCount="0" - fill="freeze" - /> - </rect> - </svg> - } - /> - <WhiteboardToolbarItem - label={'Undo Annotation'} - icon={'undo'} - onItemClick={this.handleUndo} - className={cx(styles.toolbarButton, styles.notActive)} - /> - <WhiteboardToolbarItem - label={'Clear All Annotations'} - icon={'circle_close'} - onItemClick={this.handleClearAll} - className={cx(styles.toolbarButton, styles.notActive)} - /> - {this.props.isPresenter ? - <WhiteboardToolbarItem - label={this.props.multiUser ? 'Turn multi-user mode off' : 'Tuen multi-user mode on'} - icon={this.props.multiUser ? 'multi_whiteboard' : 'whiteboard'} - onItemClick={this.handleSwitchWhiteboardMode} - className={cx(styles.toolbarButton, styles.notActive)} - /> - : null} + this.renderThicknessItem() + } + {this.renderColorItem()} + {this.renderUndoItem()} + {this.renderClearAllItem()} + {isPresenter ? + this.renderMultiUserItem() + : null } </div> </div> ); @@ -506,21 +492,10 @@ export default class WhiteboardToolbar extends Component { } WhiteboardToolbar.defaultProps = { - colors: [ - '#000000', '#ffffff', '#ff0000', '#ff8800', '#ccff00', '#00ff00', - '#00ffff', '#0088ff', '#0000ff', '#8800ff', '#ff00ff', '#c0c0c0', - ], - thicknessRadiuses: [14, 12, 10, 8, 6, 4, 2], - fontSizes: [36, 32, 28, 24, 20, 16], - annotations: [ - { icon: 'text_tool', sessionValue: 'text' }, - { icon: 'linte_tool', sessionValue: 'line' }, - { icon: 'circle_tool', sessionValue: 'ellipse' }, - { icon: 'triangle_tool', sessionValue: 'triangle' }, - { icon: 'rectangle_tool', sessionValue: 'rectangle' }, - { icon: 'pen_tool', sessionValue: 'pencil' }, - { icon: 'hand', sessionValue: 'hand' }, - ], + colors: ANNOTATION_COLORS, + thicknessRadiuses: THICKNESS_RADIUSES, + fontSizes: FONT_SIZES, + annotations: ANNOTATION_TOOLS, }; WhiteboardToolbar.propTypes = { @@ -544,14 +519,27 @@ WhiteboardToolbar.propTypes = { annotations: PropTypes.arrayOf(PropTypes.object).isRequired, // defines an array of font-sizes for the Font-size submenu of the text shape - fontSizes: PropTypes.arrayOf(PropTypes.number).isRequired, + fontSizes: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.number.isRequired, + }).isRequired, + ).isRequired, // defines an array of colors for the toolbar (color submenu) - colors: PropTypes.arrayOf(PropTypes.string).isRequired, - + colors: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.string.isRequired, + }).isRequired, + ).isRequired, // defines an array of thickness values for the toolbar and their corresponding session values - thicknessRadiuses: PropTypes.arrayOf(PropTypes.number).isRequired, + thicknessRadiuses: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.number.isRequired, + }).isRequired, + ).isRequired, // defines the physical height of the whiteboard height: PropTypes.number.isRequired, }; + +export default injectWbResizeEvent(WhiteboardToolbar); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx index b5195c1741e9e461155f66a8aa349f3b8e899b0c..7c48c619b3a9fd8543a9c593e258081dee0e7879 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx @@ -3,12 +3,23 @@ import { createContainer } from 'meteor/react-meteor-data'; import WhiteboardToolbarService from './service'; import WhiteboardToolbar from './component'; -const WhiteboardToolbarContainer = ({ ...props }) => ( +const WhiteboardToolbarContainer = props => ( <WhiteboardToolbar {...props} /> ); export default createContainer(() => ({ - actions: WhiteboardToolbarService.actions, + actions: { + undoAnnotation: WhiteboardToolbarService.undoAnnotation, + clearWhiteboard: WhiteboardToolbarService.clearWhiteboard, + changeWhiteboardMode: WhiteboardToolbarService.changeWhiteboardMode, + setInitialWhiteboardToolbarValues: WhiteboardToolbarService.setInitialWhiteboardToolbarValues, + getCurrentDrawSettings: WhiteboardToolbarService.getCurrentDrawSettings, + setFontSize: WhiteboardToolbarService.setFontSize, + setTool: WhiteboardToolbarService.setTool, + setThickness: WhiteboardToolbarService.setThickness, + setColor: WhiteboardToolbarService.setColor, + setTextShapeObject: WhiteboardToolbarService.setTextShapeObject, + }, textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(), multiUser: WhiteboardToolbarService.getMultiUserStatus(), isPresenter: WhiteboardToolbarService.isPresenter(), diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js index 0cf5ab1c5e2c4b71098226b66ed95833b6dfc835..8f3754216ecf3347974ed0725215fff29e834ba0 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js @@ -4,107 +4,80 @@ import Users from '/imports/api/2.0/users'; import Auth from '/imports/ui/services/auth'; import WhiteboardMultiUser from '/imports/api/2.0/whiteboard-multi-user/'; -const actions = { - undoAnnotation: (whiteboardId) => { - makeCall('undoAnnotation', whiteboardId); - }, - - clearWhiteboard: (whiteboardId) => { - makeCall('clearWhiteboard', whiteboardId); - }, - - changeWhiteboardMode: (multiUser) => { - makeCall('changeWhiteboardAccess', multiUser); - }, - - setInitialWhiteboardToolbarValues: (tool, thickness, color, fontSize, textShape) => { - const _drawSettings = Storage.getItem('drawSettings'); - if (!_drawSettings) { - const drawSettings = { - whiteboardAnnotationTool: tool, - whiteboardAnnotationThickness: thickness, - whiteboardAnnotationColor: color, - textFontSize: fontSize, - textShape, - }; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, - - setTool: (tool) => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - drawSettings.whiteboardAnnotationTool = tool; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, - - setThickness: (thickness) => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - drawSettings.whiteboardAnnotationThickness = thickness; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, - - setColor: (color) => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - drawSettings.whiteboardAnnotationColor = color; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, - - setFontSize: (fontSize) => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - drawSettings.textFontSize = fontSize; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, - - getCurrentDrawSettings: () => Storage.getItem('drawSettings'), - - setTextShapeObject: (textShape) => { - const drawSettings = Storage.getItem('drawSettings'); - if (drawSettings) { - drawSettings.textShape = textShape; - Storage.setItem('drawSettings', JSON.stringify(drawSettings)); - } - }, -}; +const DRAW_SETTINGS = 'drawSettings'; -const getTextShapeActiveId = () => { - const drawSettings = Storage.getItem('drawSettings'); +const makeSetter = key => (value) => { + const drawSettings = Storage.getItem(DRAW_SETTINGS); if (drawSettings) { - return drawSettings.textShape.textShapeActiveId; + drawSettings[key] = value; + Storage.setItem(DRAW_SETTINGS, drawSettings); } +}; - return ''; +const undoAnnotation = (whiteboardId) => { + makeCall('undoAnnotation', whiteboardId); }; -const getMultiUserStatus = () => { - const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID }); +const clearWhiteboard = (whiteboardId) => { + makeCall('clearWhiteboard', whiteboardId); +}; - if (data) { - return data.multiUser; +const changeWhiteboardMode = (multiUser) => { + makeCall('changeWhiteboardAccess', multiUser); +}; + +const setInitialWhiteboardToolbarValues = (tool, thickness, color, fontSize, textShape) => { + const _drawSettings = Storage.getItem(DRAW_SETTINGS); + if (!_drawSettings) { + const drawSettings = { + whiteboardAnnotationTool: tool, + whiteboardAnnotationThickness: thickness, + whiteboardAnnotationColor: color, + textFontSize: fontSize, + textShape, + }; + Storage.setItem(DRAW_SETTINGS, drawSettings); } +}; + +const getCurrentDrawSettings = () => Storage.getItem(DRAW_SETTINGS); + +const setFontSize = makeSetter('textFontSize'); + +const setTool = makeSetter('whiteboardAnnotationTool'); - return false; +const setThickness = makeSetter('whiteboardAnnotationThickness'); + +const setColor = makeSetter('whiteboardAnnotationColor'); + +const setTextShapeObject = makeSetter('textShape'); + +const getTextShapeActiveId = () => { + const drawSettings = Storage.getItem(DRAW_SETTINGS); + return drawSettings ? drawSettings.textShape.textShapeActiveId : ''; +}; + +const getMultiUserStatus = () => { + const data = WhiteboardMultiUser.findOne({ meetingId: Auth.meetingID }); + return data ? data.multiUser : false; }; const isPresenter = () => { const currentUser = Users.findOne({ userId: Auth.userID }); - - if (currentUser) { - return currentUser.presenter; - } - - return false; + return currentUser ? currentUser.presenter : false; }; export default { - actions, + undoAnnotation, + clearWhiteboard, + changeWhiteboardMode, + setInitialWhiteboardToolbarValues, + getCurrentDrawSettings, + setFontSize, + setTool, + setThickness, + setColor, + setTextShapeObject, getTextShapeActiveId, getMultiUserStatus, isPresenter, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss index 96f8696499407a2f6ff68a7fc840a05c7b4b4354..1c9b9ad62be9cfc44d7fe26fd8d2f003f653d467 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/styles.scss @@ -91,9 +91,6 @@ $number-of-vertical-main-toolbar-buttons: 5; i { color: $color-white; } -} - -.fontSizeListButton { padding: initial; } @@ -108,14 +105,11 @@ $number-of-vertical-main-toolbar-buttons: 5; } .textThickness { - font-family: "Arial"; + font-family: Arial, sans-serif; font-weight: normal; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; margin: auto; color: $color-white; - - //color transiton should match color transition for the svg Color and Thickness icons in the component.jsx - transition: color 0.4s, font-size 0.3s; } .annotationList { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/whiteboard-toolbar-item/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx similarity index 60% rename from bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/whiteboard-toolbar-item/component.jsx rename to bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx index 62b9f78a74b66e37ea160de6eb4359438e2a470f..bb782817ff3824f4f1c4d27cb5c9cd70db921996 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/whiteboard-toolbar-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-menu-item/component.jsx @@ -3,22 +3,19 @@ import PropTypes from 'prop-types'; import Button from '/imports/ui/components/button/component'; import styles from '../styles'; -export default class WhiteboardToolbarItem extends Component { +export default class ToolbarMenuItem extends Component { constructor() { super(); - this._onClick = this._onClick.bind(this); + this.handleItemClick = this.handleItemClick.bind(this); } - _onClick() { + handleItemClick() { const { objectToReturn, onItemClick } = this.props; // if there is a submenu name, then pass it to onClick - // if not - it's probably "Undo", "Clear All", "Multi-user", etc. No submenu here. - if (objectToReturn) { - onItemClick(objectToReturn); - } else { - onItemClick(); - } + // if not - it's probably "Undo", "Clear All", "Multi-user", etc. + // in the second case we'll pass undefined and it will work fine anyway + onItemClick(objectToReturn); } render() { @@ -31,42 +28,40 @@ export default class WhiteboardToolbarItem extends Component { size={'md'} label={this.props.label} icon={this.props.icon ? this.props.icon : null} - customIcon={this.props.customIcon} - onClick={this._onClick} + customIcon={this.props.customIcon ? this.props.customIcon : null} + onClick={this.handleItemClick} onBlur={this.props.onBlur} className={this.props.className} - onMouseEnter={this.props.onMouseEnter} - onMouseLeave={this.props.onMouseLeave} /> - {this.props.renderSubMenu ? this.props.renderSubMenu() : null} + {this.props.children} </div> ); } } -WhiteboardToolbarItem.propTypes = { +ToolbarMenuItem.propTypes = { + // objectToReturn, children and onBlur are passed only with menu items that have submenus + // thus they are optional + onBlur: PropTypes.func, + children: PropTypes.node, objectToReturn: PropTypes.oneOfType([ PropTypes.string, PropTypes.object, PropTypes.number, ]), onItemClick: PropTypes.func.isRequired, + // we can have either icon from the bigbluebutton-font or our custom svg/html + // thus they are optional icon: PropTypes.string, customIcon: PropTypes.node, label: PropTypes.string.isRequired, - onBlur: PropTypes.func, className: PropTypes.string.isRequired, - onMouseEnter: PropTypes.func, - onMouseLeave: PropTypes.func, - renderSubMenu: PropTypes.func, }; -WhiteboardToolbarItem.defaultProps = { +ToolbarMenuItem.defaultProps = { objectToReturn: null, icon: '', - customIcon: (<p />), + customIcon: null, onBlur: null, - onMouseLeave: null, - onMouseEnter: null, - renderSubMenu: null, + children: null, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..2ac4c999d8bf26bce8e0e4ccf5382c94a00ce0d7 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu-item/component.jsx @@ -0,0 +1,57 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Button from '/imports/ui/components/button/component'; +import styles from '../styles'; + +export default class ToolbarSubmenuItem extends Component { + constructor() { + super(); + + this.handleItemClick = this.handleItemClick.bind(this); + } + + handleItemClick() { + const { objectToReturn, onItemClick } = this.props; + // if there is a submenu name, then pass it to onClick + // if not - it's probably "Undo", "Clear All", "Multi-user", etc. + // in the second case we'll pass undefined and it will work fine anyway + onItemClick(objectToReturn); + } + + render() { + return ( + <div className={styles.buttonWrapper}> + <Button + hideLabel + role="button" + color={'default'} + size={'md'} + label={this.props.label} + icon={this.props.icon} + customIcon={this.props.customIcon} + onClick={this.handleItemClick} + className={this.props.className} + /> + </div> + ); + } +} + + +ToolbarSubmenuItem.propTypes = { + label: PropTypes.string.isRequired, + icon: PropTypes.string, + customIcon: PropTypes.node, + onItemClick: PropTypes.func.isRequired, + objectToReturn: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + PropTypes.number, + ]).isRequired, + className: PropTypes.string.isRequired, +}; + +ToolbarSubmenuItem.defaultProps = { + icon: null, + customIcon: null, +}; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..645a586a1586381e0362de9f66788d37d237c2c5 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/toolbar-submenu/component.jsx @@ -0,0 +1,134 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import styles from '../styles'; +import ToolbarSubmenuItem from '../toolbar-submenu-item/component'; + +export default class ToolbarSubmenu extends Component { + static getCustomIcon(type, obj) { + if (type === 'color') { + return ( + <svg className={styles.customSvgIcon}> + <rect x="20%" y="20%" width="60%" height="60%" fill={obj.value} /> + </svg> + ); + } else if (type === 'thickness') { + return ( + <svg className={styles.customSvgIcon}> + <circle cx="50%" cy="50%" r={obj.value} fill="#F3F6F9" /> + </svg> + ); + } else if (type === 'font-size') { + return ( + <p className={styles.textThickness} style={{ fontSize: obj.value }}> + Aa + </p> + ); + } + + return null; + } + + static getWrapperClassNames(type) { + if (type === 'color') { + return cx(styles.colorList, styles.toolbarList); + } else if (type === 'thickness') { + return cx(styles.thicknessList, styles.toolbarList); + } else if (type === 'font-size') { + return cx(styles.fontSizeList, styles.toolbarList); + } else if (type === 'annotations') { + return cx(styles.annotationList, styles.toolbarList); + } + + return null; + } + constructor() { + super(); + + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); + this.onItemClick = this.onItemClick.bind(this); + } + + onItemClick(objectToReturn) { + if (this.props.onItemClick) { + this.props.onItemClick(objectToReturn); + } + } + + handleMouseEnter() { + if (this.props.handleMouseEnter) { + this.props.handleMouseEnter(); + } + } + + handleMouseLeave() { + if (this.props.handleMouseLeave) { + this.props.handleMouseLeave(); + } + } + + render() { + const { type, objectsToRender, objectSelected, label, customIcon } = this.props; + + return ( + <div + onMouseEnter={this.handleMouseEnter} + onMouseLeave={this.handleMouseLeave} + className={ToolbarSubmenu.getWrapperClassNames(type)} + > + {objectsToRender ? objectsToRender.map(obj => + ( + <ToolbarSubmenuItem + label={label} + icon={!customIcon ? obj.icon : null} + customIcon={customIcon ? ToolbarSubmenu.getCustomIcon(type, obj) : null} + onItemClick={this.onItemClick} + objectToReturn={obj} + className={cx( + styles.toolbarListButton, + objectSelected.value === obj.value ? styles.selectedListButton : '', + )} + key={obj.value} + /> + ), + ) : null} + </div> + ); + } +} + +ToolbarSubmenu.propTypes = { + onItemClick: PropTypes.func.isRequired, + handleMouseEnter: PropTypes.func.isRequired, + handleMouseLeave: PropTypes.func.isRequired, + type: PropTypes.string.isRequired, + objectsToRender: PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.shape({ + value: PropTypes.string.isRequired, + }), + PropTypes.shape({ + value: PropTypes.number.isRequired, + }), + PropTypes.shape({ + value: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + }), + ]).isRequired, + ).isRequired, + objectSelected: PropTypes.oneOfType([ + PropTypes.shape({ + value: PropTypes.string.isRequired, + }), + PropTypes.shape({ + value: PropTypes.number.isRequired, + }), + PropTypes.shape({ + value: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + }), + ]).isRequired, + label: PropTypes.string.isRequired, + customIcon: PropTypes.bool.isRequired, +}; diff --git a/bigbluebutton-html5/imports/utils/hexInt.js b/bigbluebutton-html5/imports/utils/hexInt.js new file mode 100644 index 0000000000000000000000000000000000000000..573b50be8673a2dc32a58df644c9345be6c5b747 --- /dev/null +++ b/bigbluebutton-html5/imports/utils/hexInt.js @@ -0,0 +1,16 @@ + +export function HEXToINTColor(hexColor) { + const _rrggbb = hexColor.slice(1); + const rrggbb = _rrggbb.substr(0, 2) + _rrggbb.substr(2, 2) + _rrggbb.substr(4, 2); + return parseInt(rrggbb, 16); +} + +export function INTToHEXColor(intColor) { + let hex; + hex = parseInt(intColor, 10).toString(16); + while (hex.length < 6) { + hex = `0${hex}`; + } + + return `#${hex}`; +} diff --git a/bigbluebutton-html5/private/config/public/app.yaml b/bigbluebutton-html5/private/config/public/app.yaml index d08eedb4739c09f247ae8f6f7c7f3c8ced31c5b1..910acd7b4a87f58b1ae8067087ceb23e230c95fc 100755 --- a/bigbluebutton-html5/private/config/public/app.yaml +++ b/bigbluebutton-html5/private/config/public/app.yaml @@ -13,32 +13,32 @@ app: skipCheck: false # Default global variables - appName: "BigBlueButton HTML5 Client" - bbbServerVersion: "1.1-beta" - copyright: "©2017 BigBlueButton Inc." - html5ClientBuild: "HTML5_CLIENT_VERSION" + appName: 'BigBlueButton HTML5 Client' + bbbServerVersion: '1.1-beta' + copyright: '©2017 BigBlueButton Inc.' + html5ClientBuild: 'HTML5_CLIENT_VERSION' lockOnJoin: true # Name displayed in the brower URL basename: '/html5client' - defaultLocale: 'en' #default settings for session storage defaultSettings: application: chatAudioNotifications: false chatPushNotifications: false - fontSize: "16px" + fontSize: '16px' + locale: 'en' audio: inputDeviceId: undefined outputDeviceId: undefined video: viewParticipantsWebcams: true cc: - backgroundColor: "#FFFFFF" - fontColor: "#000000" + backgroundColor: '#FFFFFF' + fontColor: '#000000' enabled: false - fontFamily: "Calibri" + fontFamily: 'Calibri' fontSize: '16px' # locale: undefined takeOwnership: false @@ -53,4 +53,4 @@ app: # The initial client version has limited moderator capabilities # The following flag disables moderator-only features - allowHTML5Moderator: false + allowHTML5Moderator: true diff --git a/bigbluebutton-html5/private/config/public/whiteboard.yaml b/bigbluebutton-html5/private/config/public/whiteboard.yaml new file mode 100755 index 0000000000000000000000000000000000000000..40d6d0eb1ac4fdc589b17a437f3fbea0f78ac4f8 --- /dev/null +++ b/bigbluebutton-html5/private/config/public/whiteboard.yaml @@ -0,0 +1,59 @@ +whiteboard: + #annotation statuses specific to the each type of annotation + annotations: + message_frequency: 50 + status: + start: 'DRAW_START' + update: 'DRAW_UPDATE' + end: 'DRAW_END' + toolbar: + colors: + - value: '#000000' + - value: '#ffffff' + - value: '#ff0000' + - value: '#ff8800' + - value: '#ccff00' + - value: '#00ff00' + - value: '#00ffff' + - value: '#0088ff' + - value: '#0000ff' + - value: '#8800ff' + - value: '#ff00ff' + - value: '#c0c0c0' + thickness: + - value: 14 + - value: 12 + - value: 10 + - value: 8 + - value: 6 + - value: 4 + - value: 2 + font_sizes: + - value: 36 + - value: 32 + - value: 28 + - value: 24 + - value: 20 + - value: 16 + tools: + #text + - icon: 'text_tool' + value: 'text' + #line + - icon: 'linte_tool' + value: 'line' + #ellipse + - icon: 'circle_tool' + value: 'ellipse' + #triangle + - icon: 'triangle_tool' + value: 'triangle' + #rectangle + - icon: 'rectangle_tool' + value: 'rectangle' + #pencil + - icon: 'pen_tool' + value: 'pencil' + #pan and zoom hand + - icon: 'hand' + value: 'hand' diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index fd57c0ef6249c0e261ee8f0d0ab2dbfb87af93dc..65843bbc87923d1b64f351e86acb67b3463bbdc6 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -34,6 +34,8 @@ "app.userList.menu.muteUserAudio.label": "Mute user", "app.userList.menu.unmuteUserAudio.label": "Unmute user", "app.userList.userAriaLabel": "User : {0} Role: {1} Person: {2} Status: {3}", + "app.userList.menu.promoteUser.label": "Promote {0} to moderator", + "app.userList.menu.demoteUser.label": "Demote {0} to viewer", "app.media.label": "Media", "app.presentation.presentationToolbar.prevSlideLabel": "Previous slide", "app.presentation.presentationToolbar.prevSlideDesc": "Change the presentation to the previous slide", diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 6b82969d3573f170e1047e5cc6fee6533409db39..2cb809e165f82385eb00dab17cb51f8a6d5d1f97 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -109,7 +109,7 @@ defaultGuestPolicy=ASK_MODERATOR # # native2ascii -encoding UTF8 bigbluebutton.properties bigbluebutton.properties # -defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the phone button (top center of screen). Use a headset to avoid causing background noise for others.<br> +defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the phone button. Use a headset to avoid causing background noise for others.<br> defaultWelcomeMessageFooter=This server is running <a href="http://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>. # Default maximum number of users a meeting can have.