Merge remote-tracking branch 'bigbluebutton/v2.0.x-release' into client-pdf-export

# Conflicts:
#	bigbluebutton-client/branding/default/style/css/assets/swf/v2_skin.fla
#	bigbluebutton-client/branding/default/style/css/assets/swf/v2_skin.swf
This commit is contained in:
Ghazi Triki 2017-10-12 15:21:22 -03:00
commit 99e55b000a
436 changed files with 1784 additions and 5364 deletions

View File

@ -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 =>

View File

@ -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))
}

View File

@ -12,6 +12,7 @@
@namespace chat "org.bigbluebutton.modules.chat.views.*";
@namespace skins "org.bigbluebutton.skins.*";
@namespace phonecomponents "org.bigbluebutton.modules.phone.views.components.*";
@namespace tabBarClasses "flexlib.controls.tabBarClasses.*";
/*
//------------------------------
@ -125,7 +126,6 @@ mx|Application {
*/
mx|ApplicationControlBar {
borderSkin : ClassReference(null);
cornerRadius : 0;
fillAlphas : 1, 1;
fillColors : #FFFFFF, #FFFFFF;
@ -176,7 +176,8 @@ phonecomponents|MuteMeButton {
}
.defaultControlBarStyle {
color : #2A2D33;
backgroundColor : #F1F3F7;
color : #2A2D33;
}
.darkControlBarStyle {
@ -977,6 +978,7 @@ flexlib|MDIWindow {
maximizeBtnStyleName : "mdiMaximizeButtonStyle";
restoreBtnStyleName : "mdiMaximizeButtonStyle";
minimizeBtnStyleName : "mdiMinimizeButtonStyle";
closeBtnStyleName : "mdiCloseButtonStyle";
windowControlsClass : ClassReference("org.bigbluebutton.skins.WindowControlsContainer");
@ -1007,6 +1009,13 @@ flexlib|MDIWindow {
disabledSkin : Embed(source="assets/swf/v2_skin.swf", symbol="MDI_Button_Minimize_Disabled");
}
.mdiCloseButtonStyle {
upSkin : Embed(source="assets/swf/v2_skin.swf", symbol="MDI_Button_Close_Up");
overSkin : Embed(source="assets/swf/v2_skin.swf", symbol="MDI_Button_Close_Over");
downSkin : Embed(source="assets/swf/v2_skin.swf", symbol="MDI_Button_Close_Down");
disabledSkin : Embed(source="assets/swf/v2_skin.swf", symbol="MDI_Button_Close_Disabled");
}
.mdiWindowTitle {
color : #2A2D33;
fontSize : 12;
@ -1473,28 +1482,38 @@ mx|Slider {
*/
flexlib|SuperTabNavigator {
borderColor : #CDD4DB;
borderColor : #CDD4DB;
tabStyleName : "defaultTabStyle";
}
mx|Tab, .defaultTabStyle {
borderColor : #CDD4DB;
color : #2A2D33;
cornerRadius : 0;
fillColorUp : #E8EFF3;
fillColorOver : #CDD4DB;
fillColorDown : #ACB2B7;
fillColorDisabled : #F0F2F6;
iconVisible : false;
paddingBottom : 0;
paddingTop : 0;
textIndent : 4;
skin : ClassReference("org.bigbluebutton.skins.TabSkin");
.defaultTabStyle {
borderColor : #CDD4DB;
color : #2A2D33;
cornerRadius : 0;
fillColorUp : #E8EFF3;
fillColorOver : #CDD4DB;
fillColorDown : #ACB2B7;
fillColorDisabled : #F0F2F6;
iconVisible : false;
paddingBottom : 0;
paddingTop : 0;
textIndent : 4;
skin : ClassReference("org.bigbluebutton.skins.TabSkin");
tabCloseButtonStyleName : "tabCloseButton";
}
.highlightedTabStyle {
color : #DF2721;
iconColor : #DF2721;
iconVisible : true;
color : #DF2721;
iconColor : #DF2721;
iconVisible : true;
tabCloseButtonStyleName : "tabCloseButton";
}
.tabCloseButton {
upSkin : Embed(source="assets/swf/v2_skin.swf", symbol="Tab_Button_Close_Up");
overSkin : Embed(source="assets/swf/v2_skin.swf", symbol="Tab_Button_Close_Over");
downSkin : Embed(source="assets/swf/v2_skin.swf", symbol="Tab_Button_Close_Down");
disabledSkin : Embed(source="assets/swf/v2_skin.swf", symbol="Tab_Button_Close_Disabled");
}
/*
@ -1576,6 +1595,9 @@ users|StatusItemRenderer {
iconMoodThumbsUp : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_User_Mood_ThumbsUp");
iconMoodThumbsDown : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_User_Mood_ThumbsDown");
iconMoodApplause : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_User_Mood_Applause");
verticalAlign : middle;
horizontalAlign : center;
horizontalGap : 2;
}
users|MediaItemRenderer {
@ -1591,7 +1613,7 @@ users|MediaItemRenderer {
iconEject : Embed(source="assets/swf/v2_skin.swf", symbol="Icon_User_Eject");
glowFilterColor : #1070D7;
verticalAlign : middle;
horizontalGap : 8;
horizontalGap : 2;
horizontalAlign : center;
}

View File

@ -38,10 +38,9 @@
package org.bigbluebutton.skins {
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.utils.describeType;
import flash.utils.getQualifiedClassName;
import mx.core.EdgeMetrics;
import mx.core.UIComponent;
import mx.skins.Border;

View File

@ -322,6 +322,7 @@ bbb.chat.saveBtn.toolTip = Save chat
bbb.chat.saveBtn.accessibilityName = Save chat in text file
bbb.chat.saveBtn.label = Save
bbb.chat.save.complete = Chat successfully saved
bbb.chat.save.ioerror = Chat not saved. Try saving again.
bbb.chat.save.filename = public-chat
bbb.chat.copyBtn.toolTip = Copy chat
bbb.chat.copyBtn.accessibilityName = Copy chat to clipboard
@ -473,6 +474,7 @@ bbb.layout.combo.customName = Custom layout
bbb.layout.combo.remote = Remote
bbb.layout.window.name = Layout name
bbb.layout.save.complete = Layouts were successfully saved
bbb.layout.save.ioerror = Layouts were not saved. Try saving again.
bbb.layout.load.complete = Layouts were successfully loaded
bbb.layout.load.failed = Unable to load the layouts
bbb.layout.sync = Your layout has been sent to all participants
@ -553,6 +555,7 @@ bbb.sharedNotes.typing.double = {0} and {1} are typing...
bbb.sharedNotes.typing.multiple = Several people are typing...
bbb.sharedNotes.save.toolTip = Save notes to file
bbb.sharedNotes.save.complete = Notes were successfully saved
bbb.sharedNotes.save.ioerror = Notes were not saved. Try saving again.
bbb.sharedNotes.save.htmlLabel = Formatted text (.html)
bbb.sharedNotes.save.txtLabel = Plain text (.txt)
bbb.sharedNotes.new.label = Create
@ -865,54 +868,3 @@ bbb.users.roomsGrid.join = Join
bbb.users.roomsGrid.noUsers = No users is this room
bbb.langSelector.default=Default language
bbb.langSelector.ar=Arabic
bbb.langSelector.az_AZ=Azerbaijani
bbb.langSelector.eu_EU=Basque
bbb.langSelector.bn_BN=Bengali
bbb.langSelector.bg_BG=Bulgarian
bbb.langSelector.ca_ES=Catalan
bbb.langSelector.zh_CN=Chinese (Simplified)
bbb.langSelector.zh_TW=Chinese (Traditional)
bbb.langSelector.hr_HR=Croatian
bbb.langSelector.cs_CZ=Czech
bbb.langSelector.da_DK=Danish
bbb.langSelector.nl_NL=Dutch
bbb.langSelector.en_US=English
bbb.langSelector.et_EE=Estonian
bbb.langSelector.fa_IR=Farsi
bbb.langSelector.fi_FI=Finnish
bbb.langSelector.fr_FR=French
bbb.langSelector.fr_CA=French (Canadian)
bbb.langSelector.ff_SN=Fulah
bbb.langSelector.de_DE=German
bbb.langSelector.el_GR=Greek
bbb.langSelector.he_IL=Hebrew
bbb.langSelector.hu_HU=Hungarian
bbb.langSelector.id_ID=Indonesian
bbb.langSelector.it_IT=Italian
bbb.langSelector.ja_JP=Japanese
bbb.langSelector.ko_KR=Korean
bbb.langSelector.lv_LV=Latvian
bbb.langSelector.lt_LT=Lithuania
bbb.langSelector.mn_MN=Mongolian
bbb.langSelector.ne_NE=Nepali
bbb.langSelector.no_NO=Norwegian
bbb.langSelector.pl_PL=Polish
bbb.langSelector.pt_BR=Portuguese (Brazilian)
bbb.langSelector.pt_PT=Portuguese
bbb.langSelector.ro_RO=Romanian
bbb.langSelector.ru_RU=Russian
bbb.langSelector.sr_SR=Serbian (Cyrillic)
bbb.langSelector.sr_RS=Serbian (Latin)
bbb.langSelector.si_LK=Sinhala
bbb.langSelector.sk_SK=Slovak
bbb.langSelector.sl_SL=Slovenian
bbb.langSelector.es_ES=Spanish
bbb.langSelector.es_LA=Spanish (Latin American)
bbb.langSelector.sv_SE=Swedish
bbb.langSelector.th_TH=Thai
bbb.langSelector.tr_TR=Turkish
bbb.langSelector.uk_UA=Ukrainian
bbb.langSelector.vi_VN=Vietnamese
bbb.langSelector.cy_GB=Welsh
bbb.langSelector.oc=Occitan

View File

@ -1,54 +1,54 @@
<?xml version="1.0" ?>
<locales>
<locale code="ar" name="Arabic" direction="rtl"/>
<locale code="az_AZ" name="Azerbaijani"/>
<locale code="eu_EU" name="Basque" />
<locale code="bn_BN" name="Bengali" />
<locale code="bg_BG" name="Bulgarian" />
<locale code="ca_ES" name="Catalan" />
<locale code="zh_CN" name="Chinese (Simplified)"/>
<locale code="zh_TW" name="Chinese (Traditional)"/>
<locale code="hr_HR" name="Croatian" />
<locale code="cs_CZ" name="Czech" />
<locale code="da_DK" name="Danish" />
<locale code="nl_NL" name="Dutch" />
<locale code="en_US" name="English"/>
<locale code="et_EE" name="Estonian"/>
<locale code="fa_IR" name="Farsi" />
<locale code="fi_FI" name="Finnish"/>
<locale code="fr_FR" name="French"/>
<locale code="fr_CA" name="French (Canadian)"/>
<locale code="ff_SN" name="Fulah"/>
<locale code="de_DE" name="German"/>
<locale code="el_GR" name="Greek"/>
<locale code="he_IL" name="Hebrew" direction="rtl"/>
<locale code="hu_HU" name="Hungarian"/>
<locale code="id_ID" name="Indonesian"/>
<locale code="it_IT" name="Italian"/>
<locale code="ja_JP" name="Japanese"/>
<locale code="ko_KR" name="Korean"/>
<locale code="lv_LV" name="Latvian"/>
<locale code="lt_LT" name="Lithuania"/>
<locale code="mn_MN" name="Mongolian"/>
<locale code="ne_NE" name="Nepali"/>
<locale code="no_NO" name="Norwegian"/>
<locale code="oc" name="Occitan"/>
<locale code="pl_PL" name="Polish"/>
<locale code="pt_BR" name="Portuguese (Brazilian)"/>
<locale code="pt_PT" name="Portuguese"/>
<locale code="ro_RO" name="Romanian"/>
<locale code="ru_RU" name="Russian"/>
<locale code="sr_SR" name="Serbian (Cyrillic)"/>
<locale code="sr_RS" name="Serbian (Latin)"/>
<locale code="si_LK" name="Sinhala"/>
<locale code="sk_SK" name="Slovak"/>
<locale code="sl_SL" name="Slovenian"/>
<locale code="es_ES" name="Spanish"/>
<locale code="es_LA" name="Spanish (Latin American)"/>
<locale code="sv_SE" name="Swedish"/>
<locale code="th_TH" name="Thai"/>
<locale code="tr_TR" name="Turkish"/>
<locale code="uk_UA" name="Ukrainian"/>
<locale code="vi_VN" name="Vietnamese"/>
<locale code="cy_GB" name="Welsh"/>
<locale code="ar" name="Arabic" native="العربيّة" direction="rtl"/>
<locale code="az_AZ" name="Azerbaijani" native="Azərbaycan"/>
<locale code="eu_EU" name="Basque" native="Euskara"/>
<locale code="bn_BN" name="Bengali" native="বাংলা (baɛṅlā)"/>
<locale code="bg_BG" name="Bulgarian" native="Български"/>
<locale code="ca_ES" name="Catalan" native="Català"/>
<locale code="zh_CN" name="Chinese (Simplified)" native="中文(简体)"/>
<locale code="zh_TW" name="Chinese (Traditional)" native="中文(繁體)"/>
<locale code="hr_HR" name="Croatian" native="Hrvatski"/>
<locale code="cs_CZ" name="Czech" native="Čeština"/>
<locale code="da_DK" name="Danish" native="Dansk"/>
<locale code="nl_NL" name="Dutch" native="Nederlands"/>
<locale code="en_US" name="English" native="English"/>
<locale code="et_EE" name="Estonian" native="Eesti"/>
<locale code="fa_IR" name="Farsi" native="فارسى"/>
<locale code="fi_FI" name="Finnish" native="Suomi"/>
<locale code="fr_FR" name="French" native="Français"/>
<locale code="fr_CA" name="French (Canadian)" native="Français (Canadien)"/>
<locale code="ff_SN" name="Fula" native="Fulfulde"/>
<locale code="de_DE" name="German" native="Deutsch"/>
<locale code="el_GR" name="Greek" native="Ελληνικά"/>
<locale code="he_IL" name="Hebrew" native="עִבְרִית" direction="rtl"/>
<locale code="hu_HU" name="Hungarian" native="Magyar"/>
<locale code="id_ID" name="Indonesian" native="Indonesia"/>
<locale code="it_IT" name="Italian" native="Italiano"/>
<locale code="ja_JP" name="Japanese" native="日本語"/>
<locale code="ko_KR" name="Korean" native="한국어"/>
<locale code="lv_LV" name="Latvian" native="Latviešu"/>
<locale code="lt_LT" name="Lithuania" native="Lietuvių"/>
<locale code="mn_MN" name="Mongolian" native="Монгол"/>
<locale code="ne_NE" name="Nepali" native="नेपाली"/>
<locale code="no_NO" name="Norwegian" native="Norsk"/>
<locale code="oc" name="Occitan" native="Occitan"/>
<locale code="pl_PL" name="Polish" native="Polski"/>
<locale code="pt_BR" name="Portuguese (Brazilian)" native="Português (Brasileiro)"/>
<locale code="pt_PT" name="Portuguese" native="Português"/>
<locale code="ro_RO" name="Romanian" native="Român"/>
<locale code="ru_RU" name="Russian" native="Русский"/>
<locale code="sr_SR" name="Serbian (Cyrillic)" native="Српски (ћирилица)"/>
<locale code="sr_RS" name="Serbian (Latin)" native="Srpski (latinica)"/>
<locale code="si_LK" name="Sinhala" native="සිංහල"/>
<locale code="sk_SK" name="Slovak" native="Slovenčina"/>
<locale code="sl_SL" name="Slovenian" native="Slovenščina"/>
<locale code="es_ES" name="Spanish" native="Español"/>
<locale code="es_LA" name="Spanish (Latin American)" native="Español (Latinoamericano)"/>
<locale code="sv_SE" name="Swedish" native="Svenska"/>
<locale code="th_TH" name="Thai" native="ภาษาไทย"/>
<locale code="tr_TR" name="Turkish" native="Türkçe"/>
<locale code="uk_UA" name="Ukrainian" native="Українська"/>
<locale code="vi_VN" name="Vietnamese" native="Tiếng việt"/>
<locale code="cy_GB" name="Welsh" native="Cymraeg"/>
</locales>

View File

@ -25,7 +25,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
xmlns:mate="http://mate.asfusion.com/"
dataProvider="{ResourceUtil.getInstance().locales}"
change="changeLanguage()"
labelField="name"
labelField="native"
rowCount="15" width="120" height="22">
<fx:Declarations>

View File

@ -640,7 +640,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
browserPermissionHelper.currentState = "firefoxMic";
}
browserPermissionHelper.x = 50;
browserPermissionHelper.y = 150;
browserPermissionHelper.y = 200;
}
} else if (browser == "Chrome") {
if (browserPermissionHelper) {
@ -929,8 +929,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
height="150"
width="100%">
<mx:Canvas id="backgroundPlaceHolder" width="100%" height="100%" />
<views:LoadingBar id="progressBar" horizontalCenter="0" verticalCenter="0" width="280" height="15" labelWidth="280" textAlign="center"/>
<views:QuotesView id="quoteView" horizontalCenter="0" y="{this.height/2 - quoteView.height - 100}"/>
<mx:VBox horizontalCenter="0" verticalCenter="0" horizontalAlign="center" verticalGap="20">
<views:QuotesView id="quoteView"/>
<views:LoadingBar id="progressBar" width="280" height="15" labelWidth="280" textAlign="center"/>
</mx:VBox>
</views:MainCanvas>
<mx:ControlBar width="100%" height="{footerHeight}" styleName="defaultControlBarStyle" id="controlBar" paddingTop="2" paddingBottom="2" verticalAlign="middle" visible="{_showFooter}" includeInLayout="{showFooterOpt}" >

View File

@ -59,7 +59,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
quote = "Research is creating new knowledge."
}
if (StringUtils.isEmpty(attribution)) {
quote = "Neil Armstrong"
attribution = "Neil Armstrong"
}
quoteLabel.text = "“ " + quote + " ”";
quoteAttribution.text = attribution;

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.modules.chat.services
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.FileReference;
import mx.controls.Alert;
@ -23,6 +24,9 @@ package org.bigbluebutton.modules.chat.services
fileRef.addEventListener(Event.COMPLETE, function(evt:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.chat.save.complete'), "", Alert.OK);
});
fileRef.addEventListener(IOErrorEvent.IO_ERROR, function(evt:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.chat.save.ioerror'), "", Alert.OK);
});
var cr:String = String.fromCharCode(13);
var lf:String = String.fromCharCode(10);

View File

@ -23,6 +23,7 @@ package org.bigbluebutton.modules.layout.managers
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.TimerEvent;
import flash.net.FileReference;
import flash.utils.Timer;
@ -57,7 +58,7 @@ package org.bigbluebutton.modules.layout.managers
import org.bigbluebutton.modules.layout.model.LayoutLoader;
import org.bigbluebutton.modules.layout.model.LayoutModel;
import org.bigbluebutton.modules.layout.views.CustomLayoutNameWindow;
import org.bigbluebutton.util.i18n.ResourceUtil;
import org.bigbluebutton.util.i18n.ResourceUtil;
public class LayoutManager extends EventDispatcher {
private static const LOGGER:ILogger = getClassLogger(LayoutManager);
@ -142,6 +143,9 @@ package org.bigbluebutton.modules.layout.managers
_fileRef.addEventListener(Event.COMPLETE, function(e:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.layout.save.complete'), "", Alert.OK, _canvas);
});
_fileRef.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.layout.save.ioerror'), "", Alert.OK, _canvas);
});
_fileRef.save(_layoutModel.toString(), "layouts.xml");
}

View File

@ -30,7 +30,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
layout="absolute"
creationComplete="onCreationComplete()"
keyUp="keyUpHandler(event)"
width="250" xmlns:common="org.bigbluebutton.common.*">
width="240" xmlns:common="org.bigbluebutton.common.*">
<fx:Script>
<![CDATA[
@ -80,24 +80,23 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
]]>
</fx:Script>
<mx:VBox width="100%" height="100%" paddingTop="15">
<mx:VBox width="100%" height="100%" paddingTop="5" horizontalAlign="center">
<common:AdvancedLabel text="{ResourceUtil.getInstance().getString('bbb.sharedNotes.createNoteWindow.label')}"
styleName="titleWindowStyle"
maxWidth="{this.width - 10}" />
<mx:HBox width="100%"
height="100%"
verticalAlign="middle">
<mx:TextInput id="textInput"
restrict="a-zA-Z0-9 "
maxChars="20"
width="100%"
text="{sharedNoteTitle}" />
<mx:Button id="btnNew"
click="btnNew_clickHandler(event)"
height="30"
label="{ResourceUtil.getInstance().getString('bbb.sharedNotes.new.label')}"
toolTip="{ResourceUtil.getInstance().getString('bbb.sharedNotes.new.toolTip')}" />
</mx:HBox>
width="180" />
<mx:TextInput id="textInput"
restrict="a-zA-Z0-9 "
maxChars="20"
height="26"
width="100%"
text="{sharedNoteTitle}" />
<mx:Button id="btnNew"
click="btnNew_clickHandler(event)"
styleName="mainActionButton"
height="30"
width="100%"
label="{ResourceUtil.getInstance().getString('bbb.sharedNotes.new.label')}"
toolTip="{ResourceUtil.getInstance().getString('bbb.sharedNotes.new.toolTip')}" />
</mx:VBox>
<mx:Button id="closeButton" click="onCancelClicked()" styleName="titleWindowCloseButton"

View File

@ -318,11 +318,14 @@
}
protected function saveNotes(title:String, text:String, extension:String):void {
var filename:String = title.replace(/\s+/g, '-').toLowerCase() + "." + extension;
var filename:String = title.replace(/\s+/g, '_').toLowerCase() + "_" + LiveMeeting.inst().meeting.name.replace(/\s+/g, '_').toLowerCase() + "_" + new Date().time.toString() + "." + extension;
var fileRef:FileReference = new FileReference();
fileRef.addEventListener(Event.COMPLETE, function(e:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.sharedNotes.save.complete'), "", Alert.OK);
});
fileRef.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event):void {
Alert.show(ResourceUtil.getInstance().getString('bbb.sharedNotes.save.ioerror'), "", Alert.OK);
});
var cr:String = String.fromCharCode(13);
var lf:String = String.fromCharCode(10);
@ -528,7 +531,7 @@
}
protected function btnToolbar_clickHandler(event:MouseEvent):void {
richTextEditor.showControlBar = !richTextEditor.showControlBar;
btnFormat.selected = richTextEditor.showControlBar = !richTextEditor.showControlBar;
}
public function getPrefferedPosition():String {
@ -616,11 +619,6 @@
toolTip="{options.maxNoteLength - richTextEditor.text.length > 0 ?
ResourceUtil.getInstance().getString('bbb.sharedNotes.remaining.tooltip') :
ResourceUtil.getInstance().getString('bbb.sharedNotes.full.tooltip')}"/>
<mx:Button id="btnSettings"
styleName="sharedNotesSettings"
width="26" height="26"
click="btnSettings_clickHandler(event)"
toolTip="{ResourceUtil.getInstance().getString('bbb.sharedNotes.settings.toolTip')}"/>
<mx:Button id="btnFormat"
styleName="sharedNotesFormatButtonStyle"
width="26" height="26"
@ -634,6 +632,11 @@
height="26"
click="btnSave_clickHandler(event)"
toolTip="{ResourceUtil.getInstance().getString('bbb.sharedNotes.save.toolTip')}"/>
<mx:Button id="btnSettings"
styleName="sharedNotesSettings"
width="26" height="26"
click="btnSettings_clickHandler(event)"
toolTip="{ResourceUtil.getInstance().getString('bbb.sharedNotes.settings.toolTip')}"/>
</mx:HBox>
</mx:VBox>
</CustomMdiWindow>

View File

@ -24,10 +24,7 @@
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mate="http://mate.asfusion.com/"
creationComplete="onCreationComplete()"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
verticalAlign="middle"
horizontalGap="8"
horizontalAlign="center">
verticalScrollPolicy="off" horizontalScrollPolicy="off">
<fx:Declarations>
<mate:Listener type="{UsersRollEvent.USER_ROLL_OVER}" method="onRollOver" />

View File

@ -166,9 +166,9 @@ $Id: $
private const LISTEN_TO_BREAKOUT_ROOM:String = "Listen To Breakout Room";
private const JOIN_BREAKOUT_ROOM:String = "Join Breakout Room";
private const STATUS_GRID_WIDTH:int = 56;
private const STATUS_GRID_WIDTH:int = 50;
private const NAME_GRID_MIN_WIDTH:int = 80;
private const MEDIA_GRID_WIDTH:int = 118;
private const MEDIA_GRID_WIDTH:int = 100;
private var muteMeRolled:Boolean = false;

View File

@ -116,6 +116,7 @@ package org.bigbluebutton.util.i18n
locales.push({
code: DEFAULT_LOCALE_IDENTIFIER,
name: "",
native: "",
direction: "ltr"
});
@ -123,6 +124,7 @@ package org.bigbluebutton.util.i18n
locales.push({
code: locale.@code.toString(),
name: locale.@name.toString(),
native: locale.@native.toString(),
direction: locale.@direction.valueOf().toString().toLowerCase() == "rtl" ? "rtl" : "ltr"
});
}
@ -177,13 +179,7 @@ package org.bigbluebutton.util.i18n
}
private function reloadLocaleNames():void {
for each (var item:* in locales) {
if (item.code == DEFAULT_LOCALE_IDENTIFIER) {
item.name = ResourceUtil.getInstance().getString("bbb.langSelector." + item.code, null, getDefaultLocale());
} else {
item.name = ResourceUtil.getInstance().getString("bbb.langSelector." + item.code, null, preferredLocale);
}
}
locales[0].name = locales[0].native = ResourceUtil.getInstance().getString("bbb.langSelector." + locales[0].code, null, getDefaultLocale());
locales.sort(localesCompareFunction);
}

View File

@ -41,14 +41,14 @@
height: 1.5rem;
padding: 0;
border-radius: 2rem;
background-color: #4E5A66;
background-color: #DF2721;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.react-toggle--checked .react-toggle-track {
background-color: #4E5A66;
background-color: #008081;
}
.react-toggle-track-check {

View File

@ -1 +0,0 @@
export default new Mongo.Collection('breakouts');

View File

@ -1,13 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleCreateBreakout from './handlers/createBreakout';
import handleBreakoutStarted from './handlers/breakoutStarted';
import handleBreakoutJoinURL from './handlers/breakoutJoinURL';
import handleUpdateTimeRemaining from './handlers/updateTimeRemaining';
import handleBreakoutClosed from './handlers/breakoutClosed';
RedisPubSub.on('CreateBreakoutRoomRequest', handleCreateBreakout);
RedisPubSub.on('BreakoutRoomStarted', handleBreakoutStarted);
RedisPubSub.on('BreakoutRoomJoinURL', handleBreakoutJoinURL);
RedisPubSub.on('BreakoutRoomsTimeRemainingUpdate', handleUpdateTimeRemaining);
RedisPubSub.on('BreakoutRoomClosed', handleBreakoutClosed);

View File

@ -1,10 +0,0 @@
import clearBreakouts from '../modifiers/clearBreakouts';
import { check } from 'meteor/check';
export default function handleBreakoutClosed({ payload }) {
const meetingId = payload.meetingId;
check(meetingId, String);
return clearBreakouts(meetingId);
}

View File

@ -1,82 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import xml2js from 'xml2js';
import url from 'url';
const xmlParser = new xml2js.Parser();
const getUrlParams = (urlToParse) => {
const options = { parseQueryString: true };
const parsedUrl = url.parse(urlToParse, options);
return parsedUrl.query;
};
export default function handleBreakoutJoinURL({ payload }) {
const REDIS_CONFIG = Meteor.settings.redis;
const CLIENT_HTML = 'HTML5';
const {
noRedirectJoinURL,
} = payload;
check(noRedirectJoinURL, String);
const urlParams = getUrlParams(noRedirectJoinURL);
const selector = {
externalMeetingId: urlParams.meetingID,
};
let breakout = Breakouts.findOne(selector);
const res = Meteor.http.call('get', noRedirectJoinURL);
xmlParser.parseString(res.content, (err, parsedXML) => {
if (err) {
return Logger.error(`An Error occured when parsing xml response for: ${noRedirectJoinURL}`);
}
breakout = Breakouts.findOne(selector);
const { response } = parsedXML;
const users = breakout.users;
const user = {
userId: payload.userId,
urlParams: {
meetingId: response.meeting_id[0],
userId: response.user_id[0],
authToken: response.auth_token[0],
},
};
const userExists = users.find(u => user.userId === u.userId);
if (userExists) {
return;
}
const modifier = {
$push: {
users: user,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding breakout to collection: ${err}`);
}
const {
insertedId,
} = numChanged;
if (insertedId) {
return Logger.info(`Added breakout id=${urlParams.meetingID}`);
}
return Logger.info(`Upserted breakout id=${urlParams.meetingID}`);
};
return Breakouts.upsert(selector, modifier, cb);
});
}

View File

@ -1,38 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function handleBreakoutRoomStarted({ payload }) {
const {
meetingId,
timeRemaining,
externalMeetingId,
} = payload;
check(meetingId, String);
const selector = {
breakoutMeetingId: meetingId,
};
modifier = {
$set: {
users: [],
timeRemaining: Number(timeRemaining),
externalMeetingId,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`updating breakout: ${err}`);
}
if (numChanged) {
return Logger.info('Updated timeRemaining and externalMeetingId ' +
`for breakout id=${meetingId}`);
}
};
return Breakouts.update(selector, modifier, cb);
}

View File

@ -1,12 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import addBreakout from '../modifiers/addBreakout';
export default function handleCreateBreakout({ payload }) {
const { breakoutMeetingId } = payload;
check(breakoutMeetingId, String);
return addBreakout(payload);
}

View File

@ -1,40 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function handleUpdateTimeRemaining({ payload }) {
const {
meetingId,
timeRemaining,
} = payload;
check(meetingId, String);
check(timeRemaining, Number);
const selector = {
parentMeetingId: meetingId,
};
const modifier = {
$set: {
timeRemaining,
},
};
const options = {
multi: true,
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating breakouts: ${err}`);
}
if (numChanged) {
return Logger.info('Updated breakout time remaining for breakouts ' +
`where parentMeetingId=${meetingId}`);
}
};
return Breakouts.update(selector, modifier, options, cb);
}

View File

@ -1,42 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function addBreakout(payload) {
const {
breakoutMeetingId,
parentMeetingId,
name,
} = payload;
check(breakoutMeetingId, String);
check(parentMeetingId, String);
check(name, String);
const selector = { breakoutMeetingId };
const modifier = {
$set: {
breakoutMeetingId,
parentMeetingId,
name,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding breakout to collection: ${err}`);
}
const {
insertedId,
} = numChanged;
if (insertedId) {
return Logger.info(`Added breakout id=${breakoutMeetingId}`);
}
return Logger.info(`Upserted breakout id=${breakoutMeetingId}`);
};
return Breakouts.upsert(selector, modifier, cb);
}

View File

@ -1,20 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import Logger from '/imports/startup/server/logger';
import removeMeeting from '/imports/api/1.1/meetings/server/modifiers/removeMeeting';
export default function clearBreakouts(meetingId) {
if (meetingId) {
const selector = {
breakoutMeetingId: meetingId,
};
const cb = () => {
Logger.info(`Cleared Breakouts (${meetingId})`);
removeMeeting(meetingId);
};
return Breakouts.remove(selector, cb);
}
return Breakouts.remove({}, Logger.info('Cleared Breakouts (all)'));
}

View File

@ -1,28 +0,0 @@
import Breakouts from '/imports/api/1.1/breakouts';
import { Meteor } from 'meteor/meteor';
import mapToAcl from '/imports/startup/mapToAcl';
function breakouts(credentials) {
const {
meetingId,
requesterUserId,
} = credentials;
return Breakouts.find({
$or: [
{ breakoutMeetingId: meetingId },
{
users: {
$elemMatch: { userId: requesterUserId },
},
},
],
});
}
function publish(...args) {
const boundBreakouts = breakouts.bind(this);
return mapToAcl('subscriptions.breakouts', boundBreakouts)(args);
}
Meteor.publish('breakouts', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('captions');

View File

@ -1,8 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleCaptionHistory from './handlers/captionHistory';
import handleCaptionUpdate from './handlers/captionUpdate';
import handleCaptionOwnerUpdate from './handlers/captionOwnerUpdate';
RedisPubSub.on('send_caption_history_reply_message', handleCaptionHistory);
RedisPubSub.on('edit_caption_history_message', handleCaptionUpdate);
RedisPubSub.on('update_caption_owner_message', handleCaptionOwnerUpdate);

View File

@ -1,65 +0,0 @@
import _ from 'lodash';
import Captions from '/imports/api/1.1/captions';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionHistory({ payload }) {
if (!inReplyToHTML5Client({ payload })) {
return;
}
const SERVER_CONFIG = Meteor.settings.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const meetingId = payload.meeting_id;
const locale = payload.locale;
const captionHistory = payload.caption_history;
check(meetingId, String);
check(captionHistory, Object);
const captionsAdded = [];
_.each(captionHistory, (caption, locale) => {
const ownerId = caption[0];
let captions = caption[1].slice(0);
const chunks = [];
if (captions.length === 0) {
chunks.push('');
} else {
while (captions.length > 0) {
if (captions.length > CAPTION_CHUNK_LENGTH) {
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
captions = captions.slice(CAPTION_CHUNK_LENGTH);
} else {
chunks.push(captions);
captions = captions.slice(captions.length);
}
}
}
const selectorToRemove = {
meetingId,
locale,
'captionHistory.index': { $gt: (chunks.length - 1) },
};
Captions.remove(selectorToRemove);
chunks.forEach((captions, index) => {
const captionHistoryObject = {
locale,
ownerId,
captions,
index,
next: (index < chunks.length - 1) ? index + 1 : undefined,
};
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
});
});
return captionsAdded;
}

View File

@ -1,50 +0,0 @@
import Captions from '/imports/api/1.1/captions';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionOwnerUpdate({ payload }) {
const meetingId = payload.meeting_id;
const locale = payload.locale;
const ownerId = payload.owner_id;
check(meetingId, String);
check(locale, String);
check(ownerId, String);
const selector = {
meetingId,
locale,
};
const modifier = {
$set: {
'captionHistory.ownerId': ownerId,
},
};
const Caption = Captions.findOne(selector);
if (!Caption) {
const captionHistory = {
ownerId,
captions: '',
index: 0,
next: null,
};
return addCaption(meetingId, locale, captionHistory);
}
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating captions owner: ${err}`);
}
if (numChanged) {
return Logger.verbose(`Update caption owner locale=${locale} meeting=${meetingId}`);
}
};
return Captions.update(selector, modifier, { multi: true }, cb);
}

View File

@ -1,180 +0,0 @@
import Captions from '/imports/api/1.1/captions';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import addCaption from '../modifiers/addCaption';
export default function handleCaptionUpdate({ payload }) {
const SERVER_CONFIG = Meteor.settings.app;
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
const meetingId = payload.meeting_id;
const locale = payload.locale;
check(meetingId, String);
check(locale, String);
const captionsObjects = Captions.find({
meetingId,
locale,
}, {
sort: {
locale: 1,
'captionHistory.index': 1,
},
}).fetch();
const objectsToUpdate = [];
if (captionsObjects != null) {
let startIndex;
let endIndex;
let length = 0;
let current = captionsObjects[0];
// looking for a start index and end index
// (end index only for the case when they are in the same block)
while (current != null) {
length += current.captionHistory.captions.length;
// if length is bigger than start index - we found our start index
if (length >= payload.start_index && startIndex == undefined) {
// check if it's a new character somewhere in the middle of captions text
if (length - 1 >= payload.start_index) {
startIndex = payload.start_index - (length - current.captionHistory.captions.length);
// check to see if the end_index is in the same object as start_index
if (length - 1 >= payload.end_index) {
endIndex = payload.end_index - (length - current.captionHistory.captions.length);
const _captions = current.captionHistory.captions;
current.captionHistory.captions = _captions.slice(0, startIndex) +
payload.text +
_captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// end index is not in the same object as start_index, we will find it later
} else {
current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
payload.text;
objectsToUpdate.push(current);
break;
}
// separate case for appending new characters to the very end of the string
} else if (current.captionHistory.next == null &&
length == payload.start_index &&
length == payload.start_index) {
startIndex = 1;
endIndex = 1;
current.captionHistory.captions += payload.text;
objectsToUpdate.push(current);
}
}
current = captionsObjects[current.captionHistory.next];
}
// looking for end index here if it wasn't in the same object as start index
if (startIndex != undefined && endIndex == undefined) {
current = captionsObjects[current.captionHistory.next];
while (current != null) {
length += current.captionHistory.captions.length;
// check to see if the end_index is in the current object
if (length - 1 >= payload.end_index) {
endIndex = payload.end_index - (length - current.captionHistory.captions.length);
current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
objectsToUpdate.push(current);
break;
// if end_index wasn't in the current object, that means this whole object was deleted
// initializing string to ''
} else {
current.captionHistory.captions = '';
objectsToUpdate.push(current);
}
current = captionsObjects[current.captionHistory.next];
}
}
// looking for the strings which exceed the limit and split them into multiple objects
let maxIndex = captionsObjects.length - 1;
for (i = 0; i < objectsToUpdate.length; i++) {
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
// string is too large. Check if the next object exists and if it can
// accomodate the part of the string that exceeds the limits
const _nextIndex = objectsToUpdate[i].captionHistory.next;
if (_nextIndex != null &&
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
// could assign it directly, but our linter complained
let _captions = objectsToUpdate[i].captionHistory.captions;
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
objectsToUpdate[i].captionHistory.captions = _captions;
// check to see if the next object was added to objectsToUpdate array
if (objectsToUpdate[i + 1] != null &&
objectsToUpdate[i].captionHistory.next == objectsToUpdate[i + 1].captionHistory.index) {
objectsToUpdate[i + 1].captionHistory.captions = extraString +
objectsToUpdate[i + 1].captionHistory.captions;
// next object wasn't added to objectsToUpdate array, adding it from captionsObjects array.
} else {
const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
objectsToUpdate.push(nextObj);
}
// next object was full already, so we create another and insert it in between them
} else {
// need to take a current object out of the objectsToUpdate and add it back after
// every other object, so that Captions collection could be updated in a proper order
const tempObj = objectsToUpdate.splice(i, 1);
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
tempObj[0].captionHistory.captions =
tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
maxIndex += 1;
const tempIndex = tempObj[0].captionHistory.next;
tempObj[0].captionHistory.next = maxIndex;
while (extraString.length != 0) {
const entry = {
meetingId,
locale,
captionHistory: {
locale,
ownerId: tempObj[0].captionHistory.ownerId,
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
index: maxIndex,
next: null,
},
};
maxIndex += 1;
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
if (extraString.length > 0) {
entry.captionHistory.next = maxIndex;
} else {
entry.captionHistory.next = tempIndex;
}
objectsToUpdate.push(entry);
}
objectsToUpdate.push(tempObj[0]);
}
}
}
}
const captionsAdded = [];
objectsToUpdate.forEach((entry) => {
const { _id, meetingId, locale, captionHistory } = entry;
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
});
return captionsAdded;
}

View File

@ -1,49 +0,0 @@
import { check } from 'meteor/check';
import Captions from '/imports/api/1.1/captions';
import Logger from '/imports/startup/server/logger';
export default function addCaption(meetingId, locale, captionHistory, id = false) {
check(meetingId, String);
check(locale, String);
check(captionHistory, Object);
const selector = {
meetingId,
locale,
};
if (id) {
selector._id = id;
} else {
selector['captionHistory.index'] = captionHistory.index;
}
const modifier = {
$set: {
meetingId,
locale,
'captionHistory.locale': locale,
'captionHistory.ownerId': captionHistory.ownerId,
'captionHistory.captions': captionHistory.captions,
'captionHistory.next': captionHistory.next,
'captionHistory.index': captionHistory.index,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding caption to collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.verbose(`Added caption locale=${locale} meeting=${meetingId}`);
}
if (numChanged) {
return Logger.verbose(`Upserted caption locale=${locale} meeting=${meetingId}`);
}
};
return Captions.upsert(selector, modifier, cb);
}

View File

@ -1,10 +0,0 @@
import Captions from '/imports/api/1.1/captions';
import Logger from '/imports/startup/server/logger';
export default function clearCaptions(meetingId) {
if (meetingId) {
return Captions.remove({ meetingId }, Logger.info(`Cleared Captions (${meetingId})`));
}
return Captions.remove({}, Logger.info('Cleared Captions (all)'));
}

View File

@ -1,24 +0,0 @@
import Captions from '/imports/api/1.1/captions';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function captions(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
Logger.verbose(`Publishing Captions for ${meetingId} ${requesterUserId} ${requesterToken}`);
return Captions.find({ meetingId });
}
function publish(...args) {
const boundCaptions = captions.bind(this);
return mapToAcl('subscriptions.captions', boundCaptions)(args);
}
Meteor.publish('captions', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('chat');

View File

@ -1,7 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleChatMessage from './handlers/chatMessage';
import handleChatHistory from './handlers/chatHistory';
RedisPubSub.on('get_chat_history_reply', handleChatHistory);
RedisPubSub.on('send_public_chat_message', handleChatMessage);
RedisPubSub.on('send_private_chat_message', handleChatMessage);

View File

@ -1,24 +0,0 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
import addChat from '../modifiers/addChat';
export default function handleChatHistory({ payload }) {
if (!inReplyToHTML5Client({ payload })) {
return;
}
const meetingId = payload.meeting_id;
const chatHistory = payload.chat_history || [];
check(meetingId, String);
check(chatHistory, Array);
const chatsAdded = [];
chatHistory.forEach((message) => {
chatsAdded.push(addChat(meetingId, message));
});
return chatsAdded;
}

View File

@ -1,17 +0,0 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import addChat from '../modifiers/addChat';
export default function handleChatMessage({ payload, header }) {
const message = payload.message;
const meetingId = payload.meeting_id;
check(meetingId, String);
check(message, Object);
// use current_time instead of message.from_time so that the
// chats from Flash and HTML5 have uniform times
message.from_time = +(header.current_time);
return addChat(meetingId, message);
}

View File

@ -1,3 +0,0 @@
import { Meteor } from 'meteor/meteor';
Meteor.methods({});

View File

@ -1,63 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import RedisPubSub from '/imports/startup/server/redis';
import RegexWebUrl from '/imports/utils/regex-weburl';
const HTML_SAFE_MAP = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
};
const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT';
const parseMessage = (message) => {
message = message || '';
message = message.trim();
// Replace <br/> with \n\r
message = message.replace(/<br\s*[\/]?>/gi, '\n\r');
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
message = message.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]);
// Replace flash links to flash valid ones
message = message.replace(RegexWebUrl, "<a href='event:$&'><u>$&</u></a>");
return message;
};
export default function sendChat(credentials, message) {
const REDIS_CONFIG = Meteor.settings.redis;
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.chat;
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
check(message, Object);
let actionName = message.to_userid === requesterUserId ? 'chatSelf' : 'chatPrivate';
let eventName = 'send_private_chat_message';
message.message = parseMessage(message.message);
if (message.chat_type === PUBLIC_CHAT_TYPE) {
eventName = 'send_public_chat_message';
actionName = 'chatPublic';
}
const payload = {
message,
meeting_id: meetingId,
requester_id: message.from_userid,
};
return RedisPubSub.publish(CHANNEL, eventName, payload);
}

View File

@ -1,70 +0,0 @@
import Chat from '/imports/api/1.1/chat';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import { BREAK_LINE } from '/imports/utils/lineEndings.js';
const parseMessage = (message) => {
message = message || '';
// Replace \r and \n to <br/>
message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
// Replace flash links to html valid ones
message = message.split('<a href=\'event:').join('<a target="_blank" href=\'');
message = message.split('<a href="event:').join('<a target="_blank" href="');
return message;
};
export default function addChat(meetingId, message) {
// manually convert time from 1.408645053653E12 to 1408645053653 if necessary
// (this is the time_from that the Flash client outputs)
message.from_time = +(message.from_time.toString().split('.').join('').split('E')[0]);
message.message = parseMessage(message.message);
const fromUserId = message.from_userid;
const toUserId = message.to_userid;
check(fromUserId, String);
check(toUserId, String);
const selector = {
meetingId,
'message.from_time': message.from_time,
'message.from_userid': message.from_userid,
'message.to_userid': message.to_userid,
};
const modifier = {
$set: {
meetingId,
message: {
chat_type: message.chat_type,
message: message.message,
to_username: message.to_username,
from_tz_offset: message.from_tz_offset,
from_color: message.from_color,
to_userid: message.to_userid,
from_userid: message.from_userid,
from_time: message.from_time,
from_username: message.from_username,
from_lang: message.from_lang,
},
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding chat to collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
const to = message.to_username || 'PUBLIC';
return Logger.info(`Added chat id=${insertedId} from=${message.from_username} to=${to}`);
}
};
return Chat.upsert(selector, modifier, cb);
}

View File

@ -1,10 +0,0 @@
import Chat from '/imports/api/1.1/chat';
import Logger from '/imports/startup/server/logger';
export default function clearChats(meetingId) {
if (meetingId) {
return Chat.remove({ meetingId }, Logger.info(`Cleared Chats (${meetingId})`));
}
return Chat.remove({}, Logger.info('Cleared Chats (all)'));
}

View File

@ -1,25 +0,0 @@
import Chat from '/imports/api/1.1/chat';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import { BREAK_LINE } from '/imports/utils/lineEndings.js';
/**
* Remove any system message from the user with userId.
*
* @param {string} meetingId
* @param {string} userId
*/
export default function clearUserSystemMessages(meetingId, userId) {
check(meetingId, String);
check(userId, String);
const CHAT_CONFIG = Meteor.settings.public.chat;
const selector = {
meetingId,
'message.from_userid': CHAT_CONFIG.type_system,
'message.to_userid': userId,
};
return Chat.remove(selector, Logger.info(`Removing system messages from: (${userId})`));
}

View File

@ -1,41 +0,0 @@
import Chat from '/imports/api/1.1/chat';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function chat(credentials) {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
Logger.info(`Publishing chat for ${meetingId} ${requesterUserId} ${requesterToken}`);
return Chat.find({
$or: [
{
'message.chat_type': PUBLIC_CHAT_TYPE,
meetingId,
}, {
'message.from_userid': requesterUserId,
meetingId,
}, {
'message.to_userid': requesterUserId,
meetingId,
},
],
});
}
function publish(...args) {
const boundChat = chat.bind(this);
return mapToAcl('subscriptions.chat', boundChat)(args);
}
Meteor.publish('chat', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('cursor');

View File

@ -1,4 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleCursorUpdate from './handlers/cursorUpdate';
RedisPubSub.on('presentation_cursor_updated_message', handleCursorUpdate);

View File

@ -1,15 +0,0 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import updateCursor from '../modifiers/updateCursor';
export default function handleCursorUpdate({ payload }) {
const meetingId = payload.meeting_id;
const x = payload.x_percent;
const y = payload.y_percent;
check(meetingId, String);
check(x, Number);
check(y, Number);
return updateCursor(meetingId, x, y);
}

View File

@ -1,43 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
// import { isAllowedTo } from '/imports/startup/server/userPermissions';
import Presentations from '/imports/api/1.1/presentations';
export default function publishCursorUpdate(credentials, coordinates) {
const REDIS_CONFIG = Meteor.settings.redis;
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.presentation;
const EVENT_NAME = 'send_cursor_update';
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
check(coordinates, {
xPercent: Number,
yPercent: Number,
});
// if (!isAllowedTo('moveCursor', credentials)) {
// throw new Meteor.Error('not-allowed', `You are not allowed to move the Cursor`);
// }
const Presentation = Presentations.findOne({
meetingId,
'presentation.current': true,
});
if (!Presentation) {
throw new Meteor.Error(
'presentation-not-found', 'You need a presentation to be able to move the cursor');
}
const payload = {
x_percent: coordinates.xPercent,
meeting_id: meetingId,
y_percent: coordinates.yPercent,
};
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
}

View File

@ -1,8 +0,0 @@
import Cursor from '/imports/api/1.1/cursor';
import updateCursor from './updateCursor';
export default function initializeCursor(meetingId) {
check(meetingId, String);
return updateCursor(meetingId, 0, 0);
}

View File

@ -1,38 +0,0 @@
import Logger from '/imports/startup/server/logger';
import Cursor from '/imports/api/1.1/cursor';
import { check } from 'meteor/check';
export default function updateCursor(meetingId, x = 0, y = 0) {
check(meetingId, String);
check(x, Number);
check(y, Number);
const selector = {
meetingId,
};
const modifier = {
$set: {
meetingId,
x,
y,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Upserting cursor to collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Initialized cursor meeting=${meetingId}`);
}
if (numChanged) {
return Logger.debug(`Updated cursor meeting=${meetingId}`);
}
};
return Cursor.upsert(selector, modifier, cb);
}

View File

@ -1,26 +0,0 @@
import Cursor from '/imports/api/1.1/cursor';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function cursor(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
Logger.debug(`Publishing Cursor for ${meetingId} ${requesterUserId} ${requesterToken}`);
return Cursor.find({ meetingId });
}
function publish(...args) {
const boundCursor = cursor.bind(this);
return mapToAcl('subscriptions.cursor', boundCursor)(args);
}
Meteor.publish('cursor', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('deskshare');

View File

@ -1,5 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import incomingDeskshareEvent from './handlers/incomingDeskshareEvent';
RedisPubSub.on('desk_share_notify_viewers_rtmp', incomingDeskshareEvent);
RedisPubSub.on('desk_share_notify_a_single_viewer', incomingDeskshareEvent);

View File

@ -1,24 +0,0 @@
import Deskshare from '/imports/api/1.1/deskshare';
import Meetings from '/imports/api/1.1/meetings';
import modifyDeskshareStatus from '../modifiers/modifyDeskshareStatus';
import { check } from 'meteor/check';
export default function incomingDeskshareEvent({ payload }) {
check(payload, Object);
check(payload.meeting_id, String);
check(payload.broadcasting, Boolean);
check(payload.vh, Number);
check(payload.vw, Number);
const meetingId = payload.meeting_id;
const voiceBridge = Meetings.findOne({ meetingId }).voiceConf;
const deskShareInfo = {
vw: payload.vw,
vh: payload.vh,
voiceBridge, // payload.voice_bridge
broadcasting: payload.broadcasting,
};
modifyDeskshareStatus(meetingId, deskShareInfo);
}

View File

@ -1,2 +0,0 @@
import './eventHandlers';
import './publishers';

View File

@ -1,14 +0,0 @@
import Deskshare from '/imports/api/1.1/deskshare';
import { logger } from '/imports/startup/server/logger';
export function clearDeskshareCollection(meetingId) {
if (meetingId != null) {
Deskshare.remove({ meetingId }, function () {
logger.info(`cleared Deskshare Collection (meetingId: ${this.meetingId}!)`);
});
} else {
Deskshare.remove({}, () => {
logger.info('cleared Deskshare Collection (all meetings)!');
});
}
}

View File

@ -1,24 +0,0 @@
import Deskshare from '/imports/api/1.1/deskshare';
import Meetings from '/imports/api/1.1/meetings';
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/1.1/users';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export default function modifyDeskshareStatus(meetingId, deskshareInfo) {
check(meetingId, String);
const presenter = Users.findOne({ meetingId, 'user.presenter': true });
check(presenter, Object);
check(presenter.user.userid, String);
const startedById = presenter.user.userid;
Deskshare.upsert({ meetingId }, { $set: {
broadcasting: deskshareInfo.broadcasting,
timestamp: 'now',
vw: deskshareInfo.vw,
vh: deskshareInfo.vh,
voiceBridge: deskshareInfo.voiceBridge,
startedBy: startedById,
} });
}

View File

@ -1,17 +0,0 @@
import Deskshare from '/imports/api/1.1/deskshare';
import { logger } from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function deskshare(credentials) {
const { meetingId } = credentials;
logger.info(`publishing deskshare for ${meetingId}`);
return Deskshare.find({ meetingId });
}
function publish(...args) {
const boundDeskshare = deskshare.bind(this);
return mapToAcl('subscriptions.deskshare', boundDeskshare)(args);
}
Meteor.publish('deskshare', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('meetings');

View File

@ -1,17 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleMeetingDestruction from './handlers/meetingDestruction';
import handleRecordingStatusChange from './handlers/recordingStatusChange';
import handlePermissionSettingsChange from './handlers/permissionSettingsChange';
import handleMeetingCreation from './handlers/meetingCreation';
import handleGetAllMeetings from './handlers/getAllMeetings';
import handleStunTurnReply from './handlers/stunTurnReply';
RedisPubSub.on('meeting_destroyed_event', handleMeetingDestruction);
RedisPubSub.on('meeting_ended_message', handleMeetingDestruction);
RedisPubSub.on('end_and_kick_all_message', handleMeetingDestruction);
RedisPubSub.on('disconnect_all_users_message', handleMeetingDestruction);
RedisPubSub.on('recording_status_changed_message', handleRecordingStatusChange);
RedisPubSub.on('new_permission_settings', handlePermissionSettingsChange);
RedisPubSub.on('meeting_created_message', handleMeetingCreation);
RedisPubSub.on('get_all_meetings_reply_message', handleGetAllMeetings);
RedisPubSub.on('send_stun_turn_info_reply_message', handleStunTurnReply);

View File

@ -1,36 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Meetings from '/imports/api/1.1/meetings';
import addMeeting from '../modifiers/addMeeting';
import removeMeeting from '../modifiers/removeMeeting';
export default function handleGetAllMeetings({ payload }) {
let meetings = payload.meetings;
check(meetings, Array);
// We need to map the meetings payload because for some reason this payload
// is different than the `meeting_created_message` one
meetings = meetings.map(m => ({
meeting_id: m.meetingID,
name: m.meetingName,
recorded: m.recorded,
voice_conf: m.voiceBridge,
duration: m.duration,
}));
const meetingsIds = meetings.map(m => m.meeting_id);
const meetingsToRemove = Meetings.find({
meetingId: { $nin: meetingsIds },
}).fetch();
meetingsToRemove.forEach(meeting => removeMeeting(meeting.meetingId));
const meetingsAdded = [];
meetings.forEach((meeting) => {
meetingsAdded.push(addMeeting(meeting));
});
return meetingsAdded;
}

View File

@ -1,11 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import addMeeting from '../modifiers/addMeeting';
export default function handleMeetingCreation({ payload }) {
const meetingId = payload.meeting_id;
check(meetingId, String);
return addMeeting(payload);
}

View File

@ -1,11 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import removeMeeting from '../modifiers/removeMeeting';
export default function handleMeetingDestruction({ payload }) {
const meetingId = payload.meeting_id;
check(meetingId, String);
return removeMeeting(meetingId);
}

View File

@ -1,53 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Meetings from '/imports/api/1.1/meetings';
import { Meteor } from 'meteor/meteor';
import lockAllViewersMic from '/imports/api/1.1/users/server/modifiers/lockAllViewersMic';
export default function handlePermissionSettingsChange({ payload }) {
const meetingId = payload.meeting_id;
const permissions = payload.permissions;
check(meetingId, String);
check(permissions, Object);
const selector = {
meetingId,
};
const Meeting = Meetings.findOne(selector);
if (!Meeting) {
throw new Meteor.Error('meeting-not-found', `Meeting id=${meetingId} was not found`);
}
const modifier = {
$set: {
roomLockSettings: {
disablePrivateChat: permissions.disablePrivateChat,
disableCam: permissions.disableCam,
disableMic: permissions.disableMic,
lockOnJoin: permissions.lockOnJoin,
lockedLayout: permissions.lockedLayout,
disablePublicChat: permissions.disablePublicChat,
lockOnJoinConfigurable: permissions.lockOnJoinConfigurable,
},
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating meeting permissions: ${err}`);
}
if (permissions.disableMic) {
lockAllViewersMic(meetingId);
}
if (numChanged) {
return Logger.info(`Updated meeting permissions id=${meetingId}`);
}
};
return Meetings.update(selector, modifier, cb);
}

View File

@ -1,36 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Meetings from '/imports/api/1.1/meetings';
export default function handleRecordingStatusChange({ payload }) {
const meetingId = payload.meeting_id;
const intendedForRecording = payload.recorded;
const currentlyBeingRecorded = payload.recording;
check(meetingId, String);
check(intendedForRecording, Boolean);
check(currentlyBeingRecorded, Boolean);
const selector = {
meetingId,
intendedForRecording,
};
const modifier = {
$set: {
currentlyBeingRecorded,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating meeting recording status: ${err}`);
}
if (numChanged) {
return Logger.info(`Updated meeting recording status id=${meetingId}`);
}
};
return Meetings.update(selector, modifier, cb);
}

View File

@ -1,33 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Meetings from '/imports/api/1.1/meetings';
export default function handleStunTurnReply({ payload }) {
const meetingId = payload.meeting_id;
const { stuns, turns } = payload;
check(meetingId, String);
const selector = {
meetingId,
};
const modifier = {
$set: {
stuns,
turns,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating meeting stuns/turns: ${err}`);
}
if (numChanged) {
return Logger.info(`Updated meeting stuns/turns id=${meetingId}`);
}
};
return Meetings.update(selector, modifier, cb);
}

View File

@ -1,4 +0,0 @@
import { Meteor } from 'meteor/meteor';
Meteor.methods({
});

View File

@ -1,55 +0,0 @@
import { check } from 'meteor/check';
import Meetings from '/imports/api/1.1/meetings';
import Logger from '/imports/startup/server/logger';
import initializeCursor from '/imports/api/1.1/cursor/server/modifiers/initializeCursor';
export default function addMeeting(meeting) {
const APP_CONFIG = Meteor.settings.public.app;
const meetingId = meeting.meeting_id;
check(meeting, Object);
check(meetingId, String);
const selector = {
meetingId,
};
const modifier = {
$set: {
meetingId,
meetingName: meeting.name,
intendedForRecording: meeting.recorded,
currentlyBeingRecorded: false,
voiceConf: meeting.voice_conf,
duration: meeting.duration,
roomLockSettings: {
disablePrivateChat: false,
disableCam: false,
disableMic: false,
lockOnJoin: APP_CONFIG.lockOnJoin,
lockedLayout: false,
disablePublicChat: false,
lockOnJoinConfigurable: false,
},
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding meeting to collection: ${err}`);
}
initializeCursor(meetingId);
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added meeting id=${meetingId}`);
}
if (numChanged) {
return Logger.info(`Upserted meeting id=${meetingId}`);
}
};
return Meetings.upsert(selector, modifier, cb);
}

View File

@ -1,29 +0,0 @@
import Meetings from '/imports/api/1.1/chat';
import Logger from '/imports/startup/server/logger';
import removeMeeting from './removeMeeting';
import clearUsers from '/imports/api/1.1/users/server/modifiers/clearUsers';
import clearChats from '/imports/api/1.1/chat/server/modifiers/clearChats';
import clearBreakouts from '/imports/api/1.1/breakouts/server/modifiers/clearBreakouts';
import clearShapes from '/imports/api/1.1/shapes/server/modifiers/clearShapes';
import clearSlides from '/imports/api/1.1/slides/server/modifiers/clearSlides';
import clearPolls from '/imports/api/1.1/polls/server/modifiers/clearPolls';
import clearCursor from '/imports/api/1.1/cursor/server/modifiers/clearCursor';
import clearCaptions from '/imports/api/1.1/captions/server/modifiers/clearCaptions';
import clearPresentations from '/imports/api/1.1/presentations/server/modifiers/clearPresentations';
export default function clearMeetings() {
return Meetings.remove({}, (err) => {
clearCaptions();
clearChats();
clearCursor();
clearPresentations();
clearBreakouts();
clearPolls();
clearShapes();
clearSlides();
clearUsers();
return Logger.info('Cleared Meetings (all)');
});
}

View File

@ -1,41 +0,0 @@
import { check } from 'meteor/check';
import Meetings from '/imports/api/1.1/meetings';
import Logger from '/imports/startup/server/logger';
import clearUsers from '/imports/api/1.1/users/server/modifiers/clearUsers';
import clearChats from '/imports/api/1.1/chat/server/modifiers/clearChats';
import clearShapes from '/imports/api/1.1/shapes/server/modifiers/clearShapes';
import clearSlides from '/imports/api/1.1/slides/server/modifiers/clearSlides';
import clearPolls from '/imports/api/1.1/polls/server/modifiers/clearPolls';
import clearCursor from '/imports/api/1.1/cursor/server/modifiers/clearCursor';
import clearCaptions from '/imports/api/1.1/captions/server/modifiers/clearCaptions';
import clearPresentations from '/imports/api/1.1/presentations/server/modifiers/clearPresentations';
export default function removeMeeting(meetingId) {
check(meetingId, String);
const selector = {
meetingId,
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Removing meeting from collection: ${err}`);
}
if (numChanged) {
clearCaptions(meetingId);
clearChats(meetingId);
clearCursor(meetingId);
clearPresentations(meetingId);
clearPolls(meetingId);
clearShapes(meetingId);
clearSlides(meetingId);
clearUsers(meetingId);
return Logger.info(`Removed meeting id=${meetingId}`);
}
};
return Meetings.remove(selector, cb);
}

View File

@ -1,27 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Meetings from '/imports/api/1.1/meetings';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function meetings(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
Logger.info(`Publishing meeting=${meetingId} ${requesterUserId} ${requesterToken}`);
return Meetings.find({
meetingId,
});
}
function publish(...args) {
const boundMeetings = meetings.bind(this);
return mapToAcl('subscriptions.meetings', boundMeetings)(args);
}
Meteor.publish('meetings', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('polls');

View File

@ -1,9 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handlePollStopped from './handlers/pollStopped';
import handlePollStarted from './handlers/pollStarted';
import handleUserVoted from './handlers/userVoted';
RedisPubSub.on('poll_show_result_message', handlePollStopped);
RedisPubSub.on('poll_started_message', handlePollStarted);
RedisPubSub.on('poll_stopped_message', handlePollStopped);
RedisPubSub.on('user_voted_poll_message', handleUserVoted);

View File

@ -1,16 +0,0 @@
import { check } from 'meteor/check';
import addPoll from '../modifiers/addPoll';
export default function pollStarted({ payload }) {
check(payload, Object);
const meetingId = payload.meeting_id;
const requesterId = payload.requester_id;
const poll = payload.poll;
check(meetingId, String);
check(requesterId, String);
check(poll, Object);
return addPoll(meetingId, requesterId, poll);
}

View File

@ -1,22 +0,0 @@
import { check } from 'meteor/check';
import removePoll from '../modifiers/removePoll';
import clearPolls from '../modifiers/clearPolls';
export default function pollStopped({ payload }) {
check(payload, Object);
const meetingId = payload.meeting_id;
const poll = payload.poll;
check(meetingId, String);
if (poll) {
const pollId = poll.id;
check(pollId, String);
return removePoll(meetingId, pollId);
}
return clearPolls(meetingId);
}

View File

@ -1,16 +0,0 @@
import { check } from 'meteor/check';
import updateVotes from '../modifiers/updateVotes';
export default function userVoted({ payload }) {
check(payload, Object);
const meetingId = payload.meeting_id;
const poll = payload.poll;
const requesterId = payload.presenter_id;
check(meetingId, String);
check(poll, Object);
check(requesterId, String);
return updateVotes(poll, meetingId, requesterId);
}

View File

@ -1,7 +0,0 @@
import { Meteor } from 'meteor/meteor';
import publishVote1x from './methods/publishVote';
import mapToAcl from '/imports/startup/mapToAcl';
Meteor.methods(mapToAcl(['methods.publishVote'], {
publishVote1x,
}));

View File

@ -1,56 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import { check } from 'meteor/check';
import Polls from '/imports/api/1.1/polls';
import Logger from '/imports/startup/server/logger';
export default function publishVote(credentials, pollId, pollAnswerId) { // TODO discuss location
const REDIS_CONFIG = Meteor.settings.redis;
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.polling;
const EVENT_NAME = 'vote_poll_user_request_message';
const { meetingId, requesterUserId } = credentials;
const currentPoll = Polls.findOne({
users: requesterUserId,
meetingId,
'poll.answers.id': pollAnswerId,
'poll.id': pollId,
});
check(meetingId, String);
check(requesterUserId, String);
check(pollAnswerId, Number);
check(currentPoll.meetingId, String);
const payload = {
meeting_id: currentPoll.meetingId,
user_id: requesterUserId,
poll_id: currentPoll.poll.id,
question_id: 0,
answer_id: pollAnswerId,
};
const selector = {
users: requesterUserId,
meetingId,
'poll.answers.id': pollAnswerId,
};
const modifier = {
$pull: {
users: requesterUserId,
},
};
const cb = (err) => {
if (err) {
return Logger.error(`Updating Polls collection: ${err}`);
}
return Logger.info(`Updating Polls collection (meetingId: ${meetingId},
pollId: ${currentPoll.poll.id}!)`);
};
Polls.update(selector, modifier, cb);
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
}

View File

@ -1,54 +0,0 @@
import Meetings from '/imports/api/1.1/meetings';
import Users from '/imports/api/1.1/users';
import Polls from '/imports/api/1.1/polls';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function addPoll(meetingId, requesterId, poll) {
check(poll, Object);
check(requesterId, String);
check(meetingId, String);
let selector = {
meetingId,
};
const options = {
fields: {
'user.userid': 1,
_id: 0,
},
};
const userIds = Users.find(selector, options)
.fetch()
.map(user => user.user.userid);
selector = {
meetingId,
requester: requesterId,
'poll.id': poll.id,
};
const modifier = {
meetingId,
poll,
requester: requesterId,
users: userIds,
};
const cb = (err, numChanged) => {
if (err != null) {
return Logger.error(`Adding Poll to collection: ${poll.id}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added Poll id=${poll.id}`);
}
return Logger.info(`Upserted Poll id=${poll.id}`);
};
return Polls.upsert(selector, modifier, cb);
}

View File

@ -1,10 +0,0 @@
import Polls from '/imports/api/1.1/polls';
import Logger from '/imports/startup/server/logger';
export default function clearPolls(meetingId) {
if (meetingId) {
return Polls.remove({ meetingId }, Logger.info(`Cleared Polls (${meetingId})`));
}
return Polls.remove({}, Logger.info('Cleared Polls (all)'));
}

View File

@ -1,25 +0,0 @@
import Polls from '/imports/api/1.1/polls';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
export default function removePoll(meetingId, pollId) {
check(meetingId, String);
check(pollId, String);
const selector = {
meetingId,
'poll.id': pollId,
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Removing Poll from collection: ${err}`);
}
if (numChanged) {
return Logger.info(`Removed Poll id=${pollId}`);
}
};
return Polls.remove(selector, cb);
}

View File

@ -1,51 +0,0 @@
import Polls from '/imports/api/1.1/polls';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
export default function updateVotes(poll, meetingId, requesterId) {
check(meetingId, String);
check(requesterId, String);
check(poll, Object);
const {
id,
answers,
} = poll;
const numResponders = poll.num_responders;
const numRespondents = poll.num_respondents;
check(id, String);
check(answers, Array);
check(numResponders, Number);
check(numRespondents, Number);
const selector = {
meetingId,
requester: requesterId,
'poll.id': id,
};
const modifier = {
$set: {
requester: requesterId,
poll: {
answers,
num_responders: numResponders,
num_respondents: numRespondents,
id,
},
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Updating Polls collection: ${err}`);
}
Logger.info(`Updating Polls collection (meetingId: ${meetingId}, pollId: ${id}!)`);
};
return Polls.update(selector, modifier, cb);
}

View File

@ -1,27 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Polls from '/imports/api/1.1/polls';
import mapToAcl from '/imports/startup/mapToAcl';
function polls(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
const selector = {
meetingId,
users: requesterUserId,
};
return Polls.find(selector);
}
function publish(...args) {
const boundPolls = polls.bind(this);
return mapToAcl('subscriptions.polls', boundPolls)(args);
}
Meteor.publish('polls', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('presentations');

View File

@ -1,8 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handlePresentationRemove from './handlers/presentationRemove';
import handlePresentationChange from './handlers/presentationChange';
import handlePresentationInfoReply from './handlers/presentationInfoReply';
RedisPubSub.on('presentation_removed_message', handlePresentationRemove);
RedisPubSub.on('presentation_shared_message', handlePresentationChange);
RedisPubSub.on('get_presentation_info_reply', handlePresentationInfoReply);

View File

@ -1,44 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Presentations from './../../';
import addPresentation from '../modifiers/addPresentation';
const clearCurrentPresentation = (meetingId, presentationId) => {
const selector = {
meetingId,
presentationId: { $ne: presentationId },
'presentation.current': true,
};
const modifier = {
$set: { 'presentation.current': false },
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Unsetting the current presentation: ${err}`);
}
if (numChanged) {
return Logger.info('Unsetted as current presentation');
}
};
return Presentations.update(selector, modifier, cb);
};
export default function handlePresentationChange({ payload }) {
const meetingId = payload.meeting_id;
const presentation = payload.presentation;
check(meetingId, String);
check(presentation, Object);
// We need to clear the flag of the older current presentation ¯\_(ツ)_/¯
if (presentation.current) {
clearCurrentPresentation(meetingId, presentation.id);
}
return addPresentation(meetingId, presentation);
}

View File

@ -1,34 +0,0 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
import Presentations from './../../';
import addPresentation from '../modifiers/addPresentation';
import removePresentation from '../modifiers/removePresentation';
export default function handlePresentationInfoReply({ payload }) {
if (!inReplyToHTML5Client({ payload })) {
return;
}
const meetingId = payload.meeting_id;
const presentations = payload.presentations;
check(meetingId, String);
check(presentations, Array);
const presentationsIds = presentations.map(_ => _.id);
const presentationsToRemove = Presentations.find({
meetingId,
'presentation.id': { $nin: presentationsIds },
}).fetch();
presentationsToRemove.forEach(p => removePresentation(meetingId, p.presentation.id));
const presentationsAdded = [];
presentations.forEach((presentation) => {
presentationsAdded.push(addPresentation(meetingId, presentation));
});
return presentationsAdded;
}

View File

@ -1,14 +0,0 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import removePresentation from '../modifiers/removePresentation';
export default function handlePresentationRemove({ payload }) {
const meetingId = payload.meeting_id;
const presentationId = payload.presentation_id;
check(meetingId, String);
check(presentationId, String);
return removePresentation(meetingId, presentationId);
}

View File

@ -1,4 +0,0 @@
import { Meteor } from 'meteor/meteor';
Meteor.methods({
});

View File

@ -1,53 +0,0 @@
import { check } from 'meteor/check';
import Presentations from './../../';
import Logger from '/imports/startup/server/logger';
import addSlide from '/imports/api/1.1/slides/server/modifiers/addSlide';
const addSlides = (meetingId, presentationId, slides) => {
const slidesAdded = [];
slides.forEach((slide) => {
slidesAdded.push(addSlide(meetingId, presentationId, slide));
});
return slidesAdded;
};
export default function addPresentation(meetingId, presentation) {
check(meetingId, String);
check(presentation, Object);
const selector = {
meetingId,
'presentation.id': presentation.id,
};
const modifier = {
$set: {
meetingId,
'presentation.id': presentation.id,
'presentation.name': presentation.name,
'presentation.current': presentation.current,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding presentation to collection: ${err}`);
}
addSlides(meetingId, presentation.id, presentation.pages);
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added presentation id=${presentation.id} meeting=${meetingId}`);
}
if (numChanged) {
return Logger.info(`Upserted presentation id=${presentation.id} meeting=${meetingId}`);
}
};
return Presentations.upsert(selector, modifier, cb);
}

View File

@ -1,53 +0,0 @@
import { check } from 'meteor/check';
import Presentations from './../../';
import Logger from '/imports/startup/server/logger';
export default function changeCurrentPresentation(meetingId, presentationId) {
check(meetingId, String);
check(presentationId, String);
const oldCurrent = {
selector: {
meetingId,
'presentation.current': true,
},
modifier: {
$set: { 'presentation.current': false },
},
callback: (err) => {
if (err) {
return Logger.error(`Unsetting the current presentation: ${err}`);
}
return Logger.info('Unsetted as current presentation');
},
};
const newCurrent = {
selector: {
meetingId,
'presentation.id': presentationId,
},
modifier: {
$set: { 'presentation.current': true },
},
callback: (err) => {
if (err) {
return Logger.error(`Setting as current presentation id=${presentationId}: ${err}`);
}
return Logger.info(`Setted as current presentation id=${presentationId}`);
},
};
const oldPresentation = Presentations.findOne(oldCurrent.selector);
const newPresentation = Presentations.findOne(newCurrent.selector);
if (newPresentation) {
Presentations.update(newPresentation._id, newCurrent.modifier, newCurrent.callback);
}
if (oldPresentation) {
Presentations.update(oldPresentation._id, oldCurrent.modifier, oldCurrent.callback);
}
}

View File

@ -1,10 +0,0 @@
import Presentations from './../../';
import Logger from '/imports/startup/server/logger';
export default function clearPresentations(meetingId) {
if (meetingId) {
return Presentations.remove({ meetingId },
Logger.info(`Cleared Presentations (${meetingId})`));
}
return Presentations.remove({}, Logger.info('Cleared Presentations (all)'));
}

View File

@ -1,28 +0,0 @@
import { check } from 'meteor/check';
import Presentations from './../../';
import Logger from '/imports/startup/server/logger';
import clearSlidesPresentation from '/imports/api/1.1/slides/server/modifiers/clearSlidesPresentation';
export default function removePresentation(meetingId, presentationId) {
check(meetingId, String);
check(presentationId, String);
const selector = {
meetingId,
'presentation.id': presentationId,
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Removing presentation from collection: ${err}`);
}
if (numChanged) {
clearSlidesPresentation(meetingId, presentationId);
return Logger.info(`Removed presentation id=${presentationId} meeting=${meetingId}`);
}
};
return Presentations.remove(selector, cb);
}

View File

@ -1,25 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Presentations from './../';
import Logger from '/imports/startup/server/logger';
import mapToAcl from '/imports/startup/mapToAcl';
function presentations(credentials) {
const { meetingId, requesterUserId, requesterToken } = credentials;
check(meetingId, String);
check(requesterUserId, String);
check(requesterToken, String);
Logger.info(`Publishing Presentations for ${meetingId} ${requesterUserId} ${requesterToken}`);
return Presentations.find({ meetingId });
}
function publish(...args) {
const boundPresentations = presentations.bind(this);
return mapToAcl('subscriptions.presentations', boundPresentations)(args);
}
Meteor.publish('presentations', publish);

View File

@ -1 +0,0 @@
export default new Mongo.Collection('shapes');

View File

@ -1,10 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleWhiteboardGetReply from './handlers/whiteboardGetReply';
import handleWhiteboardSend from './handlers/whiteboardSend';
import handleWhiteboardCleared from './handlers/whiteboardCleared';
import handleWhiteboardUndo from './handlers/whiteboardUndo';
RedisPubSub.on('get_whiteboard_shapes_reply', handleWhiteboardGetReply);
RedisPubSub.on('send_whiteboard_shape_message', handleWhiteboardSend);
RedisPubSub.on('whiteboard_cleared_message', handleWhiteboardCleared);
RedisPubSub.on('undo_whiteboard_request', handleWhiteboardUndo);

View File

@ -1,13 +0,0 @@
import { check } from 'meteor/check';
import clearShapesWhiteboard from '../modifiers/clearShapesWhiteboard';
export default function handleWhiteboardCleared({ payload }) {
const meetingId = payload.meeting_id;
const whiteboardId = payload.whiteboard_id;
check(meetingId, String);
check(whiteboardId, String);
return clearShapesWhiteboard(meetingId, whiteboardId);
}

Some files were not shown because too many files have changed in this diff Show More