Merge branch 'master' of github.com:bigbluebutton/bigbluebutton into perroned-merge-webrtc-screenshare-2
This commit is contained in:
commit
5e0a5f019c
@ -15,6 +15,14 @@ ApplicationControlBar {
|
|||||||
dropShadowColor: #000000;
|
dropShadowColor: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.defaultControlBarStyle {
|
||||||
|
color : #0B333C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkControlBarStyle {
|
||||||
|
color : #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
Panel {
|
Panel {
|
||||||
borderColor: #dfdfdf;
|
borderColor: #dfdfdf;
|
||||||
borderAlpha: 1;
|
borderAlpha: 1;
|
||||||
@ -755,6 +763,18 @@ MDIWindow { /*None of the following properties are overridden by the MDIWindow c
|
|||||||
borderThicknessRight: 3;
|
borderThicknessRight: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.videoDockStyleFocusChatLayout {
|
||||||
|
borderStyle : none;
|
||||||
|
borderColor: #42444c;
|
||||||
|
backgroundColor: #42444c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoDockStyleNoFocusChatLayout {
|
||||||
|
borderStyle : none;
|
||||||
|
borderColor: #42444c;
|
||||||
|
backgroundColor: #42444c;
|
||||||
|
}
|
||||||
|
|
||||||
.presentationSlideViewStyle {
|
.presentationSlideViewStyle {
|
||||||
backgroundColor: #b9babc;
|
backgroundColor: #b9babc;
|
||||||
}
|
}
|
||||||
@ -865,6 +885,14 @@ MDIWindow { /*None of the following properties are overridden by the MDIWindow c
|
|||||||
borderThicknessRight: 3;
|
borderThicknessRight: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.defaultShellStyle {
|
||||||
|
backgroundColor: #fefeff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkShellStyle {
|
||||||
|
backgroundColor: #42444c;
|
||||||
|
}
|
||||||
|
|
||||||
.mdiWindowTitle {
|
.mdiWindowTitle {
|
||||||
color: #3f3f41;
|
color: #3f3f41;
|
||||||
fontFamily: Arial;
|
fontFamily: Arial;
|
||||||
|
@ -489,14 +489,17 @@ bbb.accessibility.chat.initialDescription = Please use the arrow keys to navigat
|
|||||||
bbb.accessibility.notes.notesview.input = Notes input
|
bbb.accessibility.notes.notesview.input = Notes input
|
||||||
|
|
||||||
bbb.shortcuthelp.title = Shortcut Keys
|
bbb.shortcuthelp.title = Shortcut Keys
|
||||||
|
bbb.shortcuthelp.titleBar = Shortcut Keys Window Title Bar
|
||||||
bbb.shortcuthelp.minimizeBtn.accessibilityName = Minimize the Shortcut Help Window
|
bbb.shortcuthelp.minimizeBtn.accessibilityName = Minimize the Shortcut Help Window
|
||||||
bbb.shortcuthelp.maximizeRestoreBtn.accessibilityName = Maximize the Shortcut Help Window
|
bbb.shortcuthelp.maximizeRestoreBtn.accessibilityName = Maximize the Shortcut Help Window
|
||||||
bbb.shortcuthelp.closeBtn.accessibilityName = Close the Shortcut Help Window
|
bbb.shortcuthelp.closeBtn.accessibilityName = Close the Shortcut Help Window
|
||||||
|
bbb.shortcuthelp.dropdown.accessibilityName = Shortcut Category
|
||||||
bbb.shortcuthelp.dropdown.general = Global shortcuts
|
bbb.shortcuthelp.dropdown.general = Global shortcuts
|
||||||
bbb.shortcuthelp.dropdown.presentation = Presentation shortcuts
|
bbb.shortcuthelp.dropdown.presentation = Presentation shortcuts
|
||||||
bbb.shortcuthelp.dropdown.chat = Chat shortcuts
|
bbb.shortcuthelp.dropdown.chat = Chat shortcuts
|
||||||
bbb.shortcuthelp.dropdown.users = Users shortcuts
|
bbb.shortcuthelp.dropdown.users = Users shortcuts
|
||||||
bbb.shortcuthelp.dropdown.caption = Closed Caption shortcuts
|
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 = Shortcut
|
bbb.shortcuthelp.headers.shortcut = Shortcut
|
||||||
bbb.shortcuthelp.headers.function = Function
|
bbb.shortcuthelp.headers.function = Function
|
||||||
|
|
||||||
@ -539,7 +542,7 @@ bbb.shortcutkey.logout.function = Log out of this meeting
|
|||||||
bbb.shortcutkey.raiseHand = 82
|
bbb.shortcutkey.raiseHand = 82
|
||||||
bbb.shortcutkey.raiseHand.function = Raise your hand
|
bbb.shortcutkey.raiseHand.function = Raise your hand
|
||||||
|
|
||||||
bbb.shortcutkey.present.upload = 85
|
bbb.shortcutkey.present.upload = 89
|
||||||
bbb.shortcutkey.present.upload.function = Upload presentation
|
bbb.shortcutkey.present.upload.function = Upload presentation
|
||||||
bbb.shortcutkey.present.previous = 65
|
bbb.shortcutkey.present.previous = 65
|
||||||
bbb.shortcutkey.present.previous.function = Go to previous slide
|
bbb.shortcutkey.present.previous.function = Go to previous slide
|
||||||
@ -549,19 +552,17 @@ bbb.shortcutkey.present.next = 69
|
|||||||
bbb.shortcutkey.present.next.function = Go to next slide
|
bbb.shortcutkey.present.next.function = Go to next slide
|
||||||
bbb.shortcutkey.present.fitWidth = 70
|
bbb.shortcutkey.present.fitWidth = 70
|
||||||
bbb.shortcutkey.present.fitWidth.function = Fit slides to width
|
bbb.shortcutkey.present.fitWidth.function = Fit slides to width
|
||||||
bbb.shortcutkey.present.fitPage = 80
|
bbb.shortcutkey.present.fitPage = 82
|
||||||
bbb.shortcutkey.present.fitPage.function = Fit slides to page
|
bbb.shortcutkey.present.fitPage.function = Fit slides to page
|
||||||
|
|
||||||
bbb.shortcutkey.users.makePresenter = 80
|
bbb.shortcutkey.users.makePresenter = 89
|
||||||
bbb.shortcutkey.users.makePresenter.function = Make selected person presenter
|
bbb.shortcutkey.users.makePresenter.function = Make selected person presenter
|
||||||
bbb.shortcutkey.users.kick = 75
|
bbb.shortcutkey.users.kick = 69
|
||||||
bbb.shortcutkey.users.kick.function = Kick selected person from the meeting
|
bbb.shortcutkey.users.kick.function = Kick selected person from the meeting
|
||||||
bbb.shortcutkey.users.mute = 83
|
bbb.shortcutkey.users.mute = 83
|
||||||
bbb.shortcutkey.users.mute.function = Mute or unmute selected person
|
bbb.shortcutkey.users.mute.function = Mute or unmute selected person
|
||||||
bbb.shortcutkey.users.muteall = 65
|
bbb.shortcutkey.users.muteall = 65
|
||||||
bbb.shortcutkey.users.muteall.function = Mute or unmute all users
|
bbb.shortcutkey.users.muteall.function = Mute or unmute all users
|
||||||
bbb.shortcutkey.users.focusUsers = 85
|
|
||||||
bbb.shortcutkey.users.focusUsers.function = Focus to users list
|
|
||||||
bbb.shortcutkey.users.muteAllButPres = 65
|
bbb.shortcutkey.users.muteAllButPres = 65
|
||||||
bbb.shortcutkey.users.muteAllButPres.function = Mute everyone but the Presenter
|
bbb.shortcutkey.users.muteAllButPres.function = Mute everyone but the Presenter
|
||||||
bbb.shortcutkey.users.breakoutRooms = 75
|
bbb.shortcutkey.users.breakoutRooms = 75
|
||||||
@ -570,12 +571,12 @@ bbb.shortcutkey.users.focusBreakoutRooms = 82
|
|||||||
bbb.shortcutkey.users.focusBreakoutRooms.function = Focus to breakout rooms list
|
bbb.shortcutkey.users.focusBreakoutRooms.function = Focus to breakout rooms list
|
||||||
bbb.shortcutkey.users.listenToBreakoutRoom = 76
|
bbb.shortcutkey.users.listenToBreakoutRoom = 76
|
||||||
bbb.shortcutkey.users.listenToBreakoutRoom.function = Listen to selected breakout room
|
bbb.shortcutkey.users.listenToBreakoutRoom.function = Listen to selected breakout room
|
||||||
bbb.shortcutkey.users.joinBreakoutRoom = 74
|
bbb.shortcutkey.users.joinBreakoutRoom = 79
|
||||||
bbb.shortcutkey.users.joinBreakoutRoom.function = Join selected breakout room
|
bbb.shortcutkey.users.joinBreakoutRoom.function = Join selected breakout room
|
||||||
|
|
||||||
bbb.shortcutkey.chat.focusTabs = 89
|
bbb.shortcutkey.chat.focusTabs = 89
|
||||||
bbb.shortcutkey.chat.focusTabs.function = Focus to chat tabs
|
bbb.shortcutkey.chat.focusTabs.function = Focus to chat tabs
|
||||||
bbb.shortcutkey.chat.focusBox = 66
|
bbb.shortcutkey.chat.focusBox = 77
|
||||||
bbb.shortcutkey.chat.focusBox.function = Focus to chat box
|
bbb.shortcutkey.chat.focusBox.function = Focus to chat box
|
||||||
bbb.shortcutkey.chat.changeColour = 67
|
bbb.shortcutkey.chat.changeColour = 67
|
||||||
bbb.shortcutkey.chat.changeColour.function = Focus to font color picker.
|
bbb.shortcutkey.chat.changeColour.function = Focus to font color picker.
|
||||||
|
@ -81,7 +81,7 @@ function determineGlobalAlternateModifier()
|
|||||||
// modifier = "control+alt";
|
// modifier = "control+alt";
|
||||||
//}
|
//}
|
||||||
else{
|
else{
|
||||||
modifier = "control+shift";
|
modifier = "control+shift+";
|
||||||
}
|
}
|
||||||
return modifier;
|
return modifier;
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ package org.bigbluebutton.common
|
|||||||
import flexlib.mdi.containers.MDIWindow;
|
import flexlib.mdi.containers.MDIWindow;
|
||||||
import flexlib.mdi.managers.MDIManager;
|
import flexlib.mdi.managers.MDIManager;
|
||||||
|
|
||||||
import mx.utils.ObjectUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class exists so we can properly handle context menus on MDIWindow
|
* This class exists so we can properly handle context menus on MDIWindow
|
||||||
* instances. Also, we'll be able in the future to properly handle shortcuts
|
* instances. Also, we'll be able in the future to properly handle shortcuts
|
||||||
|
@ -38,6 +38,8 @@ package org.bigbluebutton.main.model.users {
|
|||||||
|
|
||||||
public var users:ArrayCollection;
|
public var users:ArrayCollection;
|
||||||
|
|
||||||
|
public var invitedRecently : Boolean;
|
||||||
|
|
||||||
// Can be one of three following values self, none, other
|
// Can be one of three following values self, none, other
|
||||||
public var listenStatus:String = NONE;
|
public var listenStatus:String = NONE;
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ package org.bigbluebutton.main.model.users {
|
|||||||
import org.as3commons.logging.api.getClassLogger;
|
import org.as3commons.logging.api.getClassLogger;
|
||||||
import org.bigbluebutton.common.Role;
|
import org.bigbluebutton.common.Role;
|
||||||
import org.bigbluebutton.core.BBB;
|
import org.bigbluebutton.core.BBB;
|
||||||
import org.bigbluebutton.core.managers.UserManager;
|
|
||||||
import org.bigbluebutton.core.model.Config;
|
import org.bigbluebutton.core.model.Config;
|
||||||
import org.bigbluebutton.core.model.MeetingModel;
|
import org.bigbluebutton.core.model.MeetingModel;
|
||||||
import org.bigbluebutton.core.vo.CameraSettingsVO;
|
import org.bigbluebutton.core.vo.CameraSettingsVO;
|
||||||
@ -570,6 +569,19 @@ package org.bigbluebutton.main.model.users {
|
|||||||
sortBreakoutRooms();
|
sortBreakoutRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setLastBreakoutRoomInvitation(sequence:int):void {
|
||||||
|
var aRoom:BreakoutRoom;
|
||||||
|
for (var i:int = 0; i < breakoutRooms.length; i++) {
|
||||||
|
aRoom = breakoutRooms.getItemAt(i) as BreakoutRoom;
|
||||||
|
if (aRoom.sequence != sequence) {
|
||||||
|
aRoom.invitedRecently = false;
|
||||||
|
} else {
|
||||||
|
aRoom.invitedRecently = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortBreakoutRooms();
|
||||||
|
}
|
||||||
|
|
||||||
private function sortBreakoutRooms() : void {
|
private function sortBreakoutRooms() : void {
|
||||||
var sort:Sort = new Sort();
|
var sort:Sort = new Sort();
|
||||||
sort.fields = [new SortField("sequence", true, false, true)];
|
sort.fields = [new SortField("sequence", true, false, true)];
|
||||||
|
@ -69,6 +69,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
<mate:Listener type="{BreakoutRoomEvent.OPEN_BREAKOUT_ROOMS_PANEL}" method="openBreakoutRoomsWindow" />
|
<mate:Listener type="{BreakoutRoomEvent.OPEN_BREAKOUT_ROOMS_PANEL}" method="openBreakoutRoomsWindow" />
|
||||||
<mate:Listener type="{InvalidAuthTokenEvent.INVALID_AUTH_TOKEN}" method="handleInvalidAuthToken" />
|
<mate:Listener type="{InvalidAuthTokenEvent.INVALID_AUTH_TOKEN}" method="handleInvalidAuthToken" />
|
||||||
|
|
||||||
|
<mate:Listener type="{SwitchedLayoutEvent.SWITCHED_LAYOUT_EVENT}" method="onLayoutChanged" />
|
||||||
|
|
||||||
<mx:Script>
|
<mx:Script>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
import com.asfusion.mate.events.Dispatcher;
|
import com.asfusion.mate.events.Dispatcher;
|
||||||
@ -101,6 +103,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
import org.bigbluebutton.core.PopUpUtil;
|
import org.bigbluebutton.core.PopUpUtil;
|
||||||
import org.bigbluebutton.core.UsersUtil;
|
import org.bigbluebutton.core.UsersUtil;
|
||||||
import org.bigbluebutton.core.events.LockControlEvent;
|
import org.bigbluebutton.core.events.LockControlEvent;
|
||||||
|
import org.bigbluebutton.core.events.SwitchedLayoutEvent;
|
||||||
import org.bigbluebutton.core.managers.UserManager;
|
import org.bigbluebutton.core.managers.UserManager;
|
||||||
import org.bigbluebutton.core.vo.LockSettingsVO;
|
import org.bigbluebutton.core.vo.LockSettingsVO;
|
||||||
import org.bigbluebutton.main.events.AppVersionEvent;
|
import org.bigbluebutton.main.events.AppVersionEvent;
|
||||||
@ -680,6 +683,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
ExternalInterface.call("chatLinkClicked", e.text);
|
ExternalInterface.call("chatLinkClicked", e.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function onLayoutChanged(e:SwitchedLayoutEvent):void {
|
||||||
|
if (e.layoutID != "bbb.layout.name.videochat") {
|
||||||
|
this.styleName = "defaultShellStyle";
|
||||||
|
controlBar.styleName = "defaultControlBarStyle";
|
||||||
|
} else {
|
||||||
|
this.styleName = "darkShellStyle";
|
||||||
|
controlBar.styleName = "darkControlBarStyle";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
]]>
|
]]>
|
||||||
</mx:Script>
|
</mx:Script>
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
private var chatResource:Array = ['bbb.shortcutkey.chat.focusTabs', 'bbb.shortcutkey.chat.focusBox', 'bbb.shortcutkey.chat.sendMessage',
|
private var chatResource:Array = ['bbb.shortcutkey.chat.focusTabs', 'bbb.shortcutkey.chat.focusBox', 'bbb.shortcutkey.chat.sendMessage',
|
||||||
'bbb.shortcutkey.chat.closePrivate'];
|
'bbb.shortcutkey.chat.closePrivate'];
|
||||||
|
|
||||||
private var userResource:Array = ['bbb.shortcutkey.users.focusUsers', 'bbb.shortcutkey.users.makePresenter', 'bbb.shortcutkey.users.mute',
|
private var userResource:Array = ['bbb.shortcutkey.users.makePresenter', 'bbb.shortcutkey.users.mute',
|
||||||
'bbb.shortcutkey.users.kick', 'bbb.shortcutkey.users.muteall', 'bbb.shortcutkey.users.focusBreakoutRooms',
|
'bbb.shortcutkey.users.kick', 'bbb.shortcutkey.users.muteall', 'bbb.shortcutkey.users.focusBreakoutRooms',
|
||||||
'bbb.shortcutkey.users.listenToBreakoutRoom', 'bbb.shortcutkey.users.joinBreakoutRoom'];
|
'bbb.shortcutkey.users.listenToBreakoutRoom', 'bbb.shortcutkey.users.joinBreakoutRoom'];
|
||||||
|
|
||||||
@ -99,6 +99,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
private function populateModules():void{
|
private function populateModules():void{
|
||||||
categoryAC.addItem(generalString);
|
categoryAC.addItem(generalString);
|
||||||
|
|
||||||
|
if (Capabilities.playerType == "ActiveX") {
|
||||||
if (ShortcutOptions.usersActive)
|
if (ShortcutOptions.usersActive)
|
||||||
categoryAC.addItem(userString);
|
categoryAC.addItem(userString);
|
||||||
|
|
||||||
@ -107,6 +109,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
if (ShortcutOptions.chatActive)
|
if (ShortcutOptions.chatActive)
|
||||||
categoryAC.addItem(chatString);
|
categoryAC.addItem(chatString);
|
||||||
|
} else {
|
||||||
|
browserWarningText.visible = browserWarningText.includeInLayout = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function reloadKeys():void {
|
private function reloadKeys():void {
|
||||||
@ -249,10 +254,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
]]>
|
]]>
|
||||||
</mx:Script>
|
</mx:Script>
|
||||||
|
|
||||||
<common:TabIndexer id="headerIndexer" startIndex="101" tabIndices="{[minimizeBtn, maximizeRestoreBtn, closeBtn]}"/>
|
<common:TabIndexer id="headerIndexer" startIndex="115" tabIndices="{[minimizeBtn, maximizeRestoreBtn, closeBtn]}"/>
|
||||||
<common:TabIndexer startIndex="115" tabIndices="{[categories, keyList]}"/>
|
<common:TabIndexer startIndex="119" tabIndices="{[categories, keyList]}"/>
|
||||||
|
|
||||||
<mx:ComboBox id="categories" labelField="Please select an area for which to view shortcut keys: "
|
<mx:ComboBox id="categories" accessibilityName="{ResourceUtil.getInstance().getString('bbb.shortcuthelp.dropdown.accessibilityName')}"
|
||||||
editable="false"
|
editable="false"
|
||||||
change="changeArray()">
|
change="changeArray()">
|
||||||
<mx:ArrayCollection id="categoryAC">
|
<mx:ArrayCollection id="categoryAC">
|
||||||
@ -264,6 +269,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
-->
|
-->
|
||||||
</mx:ArrayCollection>
|
</mx:ArrayCollection>
|
||||||
</mx:ComboBox>
|
</mx:ComboBox>
|
||||||
|
<mx:Text id="browserWarningText" visible="false" includeInLayout="false" text="{ResourceUtil.getInstance().getString('bbb.shortcuthelp.browserWarning.text')}" />
|
||||||
<mx:DataGrid id="keyList" draggableColumns="false" dataProvider="{shownKeys}" width="100%" height="100%">
|
<mx:DataGrid id="keyList" draggableColumns="false" dataProvider="{shownKeys}" width="100%" height="100%">
|
||||||
<mx:columns>
|
<mx:columns>
|
||||||
<mx:DataGridColumn dataField="shortcut" width="150" headerText="{ResourceUtil.getInstance().getString('bbb.shortcuthelp.headers.shortcut')}"/>
|
<mx:DataGridColumn dataField="shortcut" width="150" headerText="{ResourceUtil.getInstance().getString('bbb.shortcuthelp.headers.shortcut')}"/>
|
||||||
|
@ -235,8 +235,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function focusWindow(e:ShortcutEvent):void {
|
private function focusWindow(e:ShortcutEvent):void {
|
||||||
|
if (this.visible) {
|
||||||
focusManager.setFocus(titleBarOverlay);
|
focusManager.setFocus(titleBarOverlay);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]]>
|
]]>
|
||||||
</mx:Script>
|
</mx:Script>
|
||||||
<mx:Box width="100%" height="100%" horizontalAlign="left">
|
<mx:Box width="100%" height="100%" horizontalAlign="left">
|
||||||
|
@ -120,8 +120,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function focusWindow(e:ShortcutEvent):void{
|
private function focusWindow(e:ShortcutEvent):void{
|
||||||
|
if (this.visible) {
|
||||||
focusManager.setFocus(titleBarOverlay);
|
focusManager.setFocus(titleBarOverlay);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function fullScreenHandler(evt:FullScreenEvent):void {
|
private function fullScreenHandler(evt:FullScreenEvent):void {
|
||||||
/* dispState = FlexGlobals.topLevelApplication.stage.displayState + " (fullScreen=" + evt.fullScreen.toString() + ")";
|
/* dispState = FlexGlobals.topLevelApplication.stage.displayState + " (fullScreen=" + evt.fullScreen.toString() + ")";
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package org.bigbluebutton.modules.layout.events
|
|
||||||
{
|
|
||||||
import flash.events.Event;
|
|
||||||
|
|
||||||
public class LayoutChangedEvent extends Event
|
|
||||||
{
|
|
||||||
public static const LAYOUT_CHANGED:String = "layout changed event";
|
|
||||||
|
|
||||||
public var layoutName:String;
|
|
||||||
|
|
||||||
public function LayoutChangedEvent(layoutName:String)
|
|
||||||
{
|
|
||||||
super(LAYOUT_CHANGED, true, false);
|
|
||||||
this.layoutName = layoutName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -313,7 +313,8 @@ package org.bigbluebutton.modules.layout.managers
|
|||||||
private function updateCurrentLayout(layout:LayoutDefinition):LayoutDefinition {
|
private function updateCurrentLayout(layout:LayoutDefinition):LayoutDefinition {
|
||||||
//trace(LOG + "updateCurrentLayout");
|
//trace(LOG + "updateCurrentLayout");
|
||||||
if (layout != null) {
|
if (layout != null) {
|
||||||
if (_currentLayout) _currentLayout.currentLayout = false;
|
if (_currentLayout)
|
||||||
|
_currentLayout.currentLayout = false;
|
||||||
_currentLayout = layout;
|
_currentLayout = layout;
|
||||||
//trace(LOG + "updateCurrentLayout - currentLayout = [" + layout.name + "]");
|
//trace(LOG + "updateCurrentLayout - currentLayout = [" + layout.name + "]");
|
||||||
layout.currentLayout = true;
|
layout.currentLayout = true;
|
||||||
|
@ -246,8 +246,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function focusWindow(e:ShortcutEvent):void{
|
private function focusWindow(e:ShortcutEvent):void{
|
||||||
|
if (this.visible) {
|
||||||
focusManager.setFocus(titleBarOverlay);
|
focusManager.setFocus(titleBarOverlay);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function resizeHandler():void {
|
private function resizeHandler():void {
|
||||||
// When the window is maximized, we want to resize the slide maintaining the aspect ratio.
|
// When the window is maximized, we want to resize the slide maintaining the aspect ratio.
|
||||||
|
@ -20,10 +20,11 @@ package org.bigbluebutton.modules.users.services
|
|||||||
{
|
{
|
||||||
import com.asfusion.mate.events.Dispatcher;
|
import com.asfusion.mate.events.Dispatcher;
|
||||||
|
|
||||||
|
import flash.utils.setTimeout;
|
||||||
|
|
||||||
import org.as3commons.lang.StringUtils;
|
import org.as3commons.lang.StringUtils;
|
||||||
import org.as3commons.logging.api.ILogger;
|
import org.as3commons.logging.api.ILogger;
|
||||||
import org.as3commons.logging.api.getClassLogger;
|
import org.as3commons.logging.api.getClassLogger;
|
||||||
import org.bigbluebutton.common.Role;
|
|
||||||
import org.bigbluebutton.core.BBB;
|
import org.bigbluebutton.core.BBB;
|
||||||
import org.bigbluebutton.core.EventConstants;
|
import org.bigbluebutton.core.EventConstants;
|
||||||
import org.bigbluebutton.core.UsersUtil;
|
import org.bigbluebutton.core.UsersUtil;
|
||||||
@ -649,11 +650,17 @@ package org.bigbluebutton.modules.users.services
|
|||||||
|
|
||||||
private function handleBreakoutRoomJoinURL(msg:Object):void{
|
private function handleBreakoutRoomJoinURL(msg:Object):void{
|
||||||
var map:Object = JSON.parse(msg.msg);
|
var map:Object = JSON.parse(msg.msg);
|
||||||
|
var externalMeetingId : String = StringUtils.substringBetween(map.redirectJoinURL, "meetingID=", "&");
|
||||||
|
var breakoutRoom : BreakoutRoom = UserManager.getInstance().getConference().getBreakoutRoomByExternalId(externalMeetingId);
|
||||||
|
var sequence : int = breakoutRoom.sequence;
|
||||||
|
|
||||||
var event : BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.BREAKOUT_JOIN_URL);
|
var event : BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.BREAKOUT_JOIN_URL);
|
||||||
event.joinURL = map.redirectJoinURL;
|
event.joinURL = map.redirectJoinURL;
|
||||||
var externalMeetingId : String = StringUtils.substringBetween(event.joinURL, "meetingID=", "&");
|
event.breakoutMeetingSequence = sequence;
|
||||||
event.breakoutMeetingSequence = UserManager.getInstance().getConference().getBreakoutRoomByExternalId(externalMeetingId).sequence;
|
|
||||||
dispatcher.dispatchEvent(event);
|
dispatcher.dispatchEvent(event);
|
||||||
|
|
||||||
|
// We delay assigning last room invitation sequence to be sure it is handle in time by the item renderer
|
||||||
|
setTimeout(function() : void {UserManager.getInstance().getConference().setLastBreakoutRoomInvitation(sequence)}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleUpdateBreakoutUsers(msg:Object):void{
|
private function handleUpdateBreakoutUsers(msg:Object):void{
|
||||||
|
@ -155,7 +155,7 @@
|
|||||||
var ls:LockSettingsVO = UserManager.getInstance().getConference().getLockSettings();
|
var ls:LockSettingsVO = UserManager.getInstance().getConference().getLockSettings();
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
kickUserBtn.visible = !data.me && rolledOver && options.allowKickUser;
|
kickUserBtn.visible = !data.me && rolledOver && options.allowKickUser && !UserManager.getInstance().getConference().isBreakout;
|
||||||
|
|
||||||
if (!data.voiceJoined) {
|
if (!data.voiceJoined) {
|
||||||
if (data.listenOnly) {
|
if (data.listenOnly) {
|
||||||
|
@ -21,7 +21,12 @@
|
|||||||
[Bindable]
|
[Bindable]
|
||||||
private var images:Images = new Images();
|
private var images:Images = new Images();
|
||||||
|
|
||||||
|
[Bindable]
|
||||||
|
private var moderator:Boolean = false;
|
||||||
|
|
||||||
protected function onCreationCompleteHandler(event:FlexEvent):void {
|
protected function onCreationCompleteHandler(event:FlexEvent):void {
|
||||||
|
moderator = UserManager.getInstance().getConference().amIModerator();
|
||||||
|
|
||||||
this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler);
|
this.addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,13 +56,19 @@
|
|||||||
]]>
|
]]>
|
||||||
</mx:Script>
|
</mx:Script>
|
||||||
|
|
||||||
<mx:Button id="joinBtn" width="20" height="20"
|
<mx:Button id="joinBtn"
|
||||||
includeInLayout="{UserManager.getInstance().getConference().breakoutRoomsReady}" visible="{joinBtn.includeInLayout}"
|
width="20"
|
||||||
icon="{images.join}" toolTip="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.join')}"
|
height="20"
|
||||||
|
icon="{images.join}"
|
||||||
|
visible="{(UserManager.getInstance().getConference().breakoutRoomsReady && moderator) || (!moderator && data.invitedRecently)}"
|
||||||
|
toolTip="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.join')}"
|
||||||
click="requestBreakoutJoinUrl(event)" />
|
click="requestBreakoutJoinUrl(event)" />
|
||||||
<mx:Button id="listenBtn" toggle="true"
|
<mx:Button id="listenBtn"
|
||||||
width="20" height="20"
|
toggle="true"
|
||||||
visible="{data.listenStatus != BreakoutRoom.OTHER && UserManager.getInstance().getConference().voiceJoined || data.listenStatus == BreakoutRoom.SELF}" includeInLayout="{listenBtn.visible}"
|
width="20"
|
||||||
icon="{images.transfer}" toolTip="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.transfer')}"
|
height="20"
|
||||||
|
visible="{moderator && data.listenStatus != BreakoutRoom.OTHER && UserManager.getInstance().getConference().voiceJoined || data.listenStatus == BreakoutRoom.SELF}"
|
||||||
|
icon="{images.transfer}"
|
||||||
|
toolTip="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.transfer')}"
|
||||||
click="listenToBreakoutRoom(event)" />
|
click="listenToBreakoutRoom(event)" />
|
||||||
</mx:HBox>
|
</mx:HBox>
|
||||||
|
23
bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml
Normal file → Executable file
23
bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml
Normal file → Executable file
@ -128,7 +128,6 @@
|
|||||||
|
|
||||||
private var joinAlert : Alert;
|
private var joinAlert : Alert;
|
||||||
|
|
||||||
private const FOCUS_USERS_LIST:String = "Focus Users List";
|
|
||||||
private const MAKE_PRESENTER:String = "Make Presenter";
|
private const MAKE_PRESENTER:String = "Make Presenter";
|
||||||
private const KICK_USER:String = "Kick User";
|
private const KICK_USER:String = "Kick User";
|
||||||
private const MUTE_USER:String = "Mute User";
|
private const MUTE_USER:String = "Mute User";
|
||||||
@ -440,9 +439,10 @@
|
|||||||
|
|
||||||
private function loadKeyCombos(modifier:String):void {
|
private function loadKeyCombos(modifier:String):void {
|
||||||
keyCombos = new Object(); // always start with a fresh array bbb.shortcutkey.users.muteall
|
keyCombos = new Object(); // always start with a fresh array bbb.shortcutkey.users.muteall
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.focusUsers') as String)] = FOCUS_USERS_LIST;
|
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.makePresenter') as String)] = MAKE_PRESENTER;
|
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.makePresenter') as String)] = MAKE_PRESENTER;
|
||||||
|
if (!UserManager.getInstance().getConference().isBreakout) {
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.kick') as String)] = KICK_USER;
|
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.kick') as String)] = KICK_USER;
|
||||||
|
}
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.mute') as String)] = MUTE_USER;
|
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.mute') as String)] = MUTE_USER;
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.muteall') as String)] = MUTE_ALL_USER;
|
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.muteall') as String)] = MUTE_ALL_USER;
|
||||||
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.focusBreakoutRooms') as String)] = FOCUS_BREAKOUT_ROOMS_LIST;
|
keyCombos[modifier + (ResourceUtil.getInstance().getString('bbb.shortcutkey.users.focusBreakoutRooms') as String)] = FOCUS_BREAKOUT_ROOMS_LIST;
|
||||||
@ -459,9 +459,6 @@
|
|||||||
var keyPress:String = KeyboardUtil.buildPressedKeys(e);
|
var keyPress:String = KeyboardUtil.buildPressedKeys(e);
|
||||||
if (keyCombos[keyPress]) {
|
if (keyCombos[keyPress]) {
|
||||||
switch (keyCombos[keyPress]) {
|
switch (keyCombos[keyPress]) {
|
||||||
case FOCUS_USERS_LIST:
|
|
||||||
remoteFocusUsers();
|
|
||||||
break;
|
|
||||||
case MAKE_PRESENTER:
|
case MAKE_PRESENTER:
|
||||||
remoteMakePresenter();
|
remoteMakePresenter();
|
||||||
break;
|
break;
|
||||||
@ -498,8 +495,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function focusWindow(e:ShortcutEvent):void {
|
private function focusWindow(e:ShortcutEvent):void {
|
||||||
|
if (this.visible) {
|
||||||
focusManager.setFocus(titleBarOverlay);
|
focusManager.setFocus(titleBarOverlay);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function remoteRaiseHand(e:ShortcutEvent):void{
|
public function remoteRaiseHand(e:ShortcutEvent):void{
|
||||||
if (UserManager.getInstance().getConference().myEmojiStatus == "raiseHand")
|
if (UserManager.getInstance().getConference().myEmojiStatus == "raiseHand")
|
||||||
@ -536,7 +535,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function remoteKickUser():void {
|
public function remoteKickUser():void {
|
||||||
if (amIModerator && usersGrid.selectedIndex != -1 && partOptions.allowKickUser) {
|
if (amIModerator && usersGrid.selectedIndex != -1 && partOptions.allowKickUser && !UserManager.getInstance().getConference().isBreakout) {
|
||||||
var selData:Object = usersGrid.selectedItem;
|
var selData:Object = usersGrid.selectedItem;
|
||||||
|
|
||||||
if (!selData.me)
|
if (!selData.me)
|
||||||
@ -557,13 +556,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function remoteFocusUsers():void {
|
|
||||||
focusManager.setFocus(usersGrid);
|
|
||||||
usersGrid.drawFocus(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remoteFocusBreakoutRooms() : void {
|
public function remoteFocusBreakoutRooms() : void {
|
||||||
if (roomsGrid && roomsGrid.visible) {
|
if (roomsGrid && roomsBox.visible) {
|
||||||
focusManager.setFocus(roomsGrid);
|
focusManager.setFocus(roomsGrid);
|
||||||
roomsGrid.drawFocus(true);
|
roomsGrid.drawFocus(true);
|
||||||
}
|
}
|
||||||
@ -636,8 +630,8 @@
|
|||||||
</views:BBBDataGrid>
|
</views:BBBDataGrid>
|
||||||
|
|
||||||
<mx:VBox id="roomsBox" styleName="breakoutRoomsBox"
|
<mx:VBox id="roomsBox" styleName="breakoutRoomsBox"
|
||||||
visible="{breakoutRoomsList.length > 0 && amIModerator}"
|
visible="{breakoutRoomsList.length > 0}"
|
||||||
includeInLayout="{breakoutRoomsList.length > 0 && amIModerator}"
|
includeInLayout="{breakoutRoomsList.length > 0}"
|
||||||
horizontalScrollPolicy="off"
|
horizontalScrollPolicy="off"
|
||||||
width="100%" height="180">
|
width="100%" height="180">
|
||||||
<mx:HBox width="100%">
|
<mx:HBox width="100%">
|
||||||
@ -658,7 +652,6 @@
|
|||||||
showDataTips="true"
|
showDataTips="true"
|
||||||
headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/>
|
headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/>
|
||||||
<mx:DataGridColumn dataField="meetingId"
|
<mx:DataGridColumn dataField="meetingId"
|
||||||
visible="{amIModerator}"
|
|
||||||
headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}"
|
headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}"
|
||||||
itemRenderer="org.bigbluebutton.modules.users.views.RoomActionsRenderer"/>
|
itemRenderer="org.bigbluebutton.modules.users.views.RoomActionsRenderer"/>
|
||||||
</views:columns>
|
</views:columns>
|
||||||
|
@ -1,260 +0,0 @@
|
|||||||
<?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/>.
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<pubVid:VideoWindowItf
|
|
||||||
xmlns:mx="http://www.adobe.com/2006/mxml"
|
|
||||||
xmlns:pubVid="org.bigbluebutton.modules.videoconf.business.*"
|
|
||||||
implements="org.bigbluebutton.common.IBbbModuleWindow"
|
|
||||||
styleNameFocus="videoAvatarStyleFocus"
|
|
||||||
styleNameNoFocus="videoAvatarStyleNoFocus"
|
|
||||||
creationComplete="onCreationComplete()"
|
|
||||||
width="{defaultWidth + 6}" height="{defaultHeight + 6}"
|
|
||||||
xmlns:mate="http://mate.asfusion.com/"
|
|
||||||
resize="onResize()"
|
|
||||||
horizontalScrollPolicy="off"
|
|
||||||
verticalScrollPolicy="off"
|
|
||||||
layout="absolute">
|
|
||||||
|
|
||||||
<mate:Listener type="{BBBEvent.USER_VOICE_MUTED}" method="handleUserVoiceMutedEvent" />
|
|
||||||
<mate:Listener type="{EventConstants.USER_TALKING}" method="handleUserTalkingEvent" />
|
|
||||||
<mate:Listener type="{SwitchedPresenterEvent.SWITCHED_PRESENTER}" method="handleSwitchedPresenterEvent" />
|
|
||||||
<mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="handleMadePresenterEvent" />
|
|
||||||
<mate:Listener type="{BBBEvent.USER_VOICE_JOINED}" method="handleNewRoleEvent" />
|
|
||||||
<mate:Listener type="{BBBEvent.USER_VOICE_LEFT}" method="handleNewRoleEvent" />
|
|
||||||
<mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" />
|
|
||||||
|
|
||||||
<mx:Script>
|
|
||||||
<![CDATA[
|
|
||||||
import org.as3commons.logging.api.getClassLogger;
|
|
||||||
import org.as3commons.logging.api.ILogger;
|
|
||||||
import org.bigbluebutton.modules.users.views.UsersWindow;
|
|
||||||
import flexlib.mdi.events.MDIWindowEvent;
|
|
||||||
|
|
||||||
import mx.core.UIComponent;
|
|
||||||
import mx.events.ResizeEvent;
|
|
||||||
|
|
||||||
import org.bigbluebutton.common.Images;
|
|
||||||
import org.bigbluebutton.common.Role;
|
|
||||||
import org.bigbluebutton.common.events.CloseWindowEvent;
|
|
||||||
import org.bigbluebutton.common.events.LocaleChangeEvent;
|
|
||||||
import org.bigbluebutton.core.EventConstants;
|
|
||||||
import org.bigbluebutton.core.UsersUtil;
|
|
||||||
import org.bigbluebutton.core.events.CoreEvent;
|
|
||||||
import org.bigbluebutton.core.events.SwitchedLayoutEvent;
|
|
||||||
import org.bigbluebutton.core.managers.UserManager;
|
|
||||||
import org.bigbluebutton.main.events.BBBEvent;
|
|
||||||
import org.bigbluebutton.main.events.MadePresenterEvent;
|
|
||||||
import org.bigbluebutton.main.events.SwitchedPresenterEvent;
|
|
||||||
import org.bigbluebutton.main.views.MainCanvas;
|
|
||||||
import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay;
|
|
||||||
import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent;
|
|
||||||
import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent;
|
|
||||||
import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent;
|
|
||||||
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
|
|
||||||
import org.bigbluebutton.util.i18n.ResourceUtil;
|
|
||||||
|
|
||||||
private static const LOGGER:ILogger = getClassLogger(AvatarWindow);
|
|
||||||
|
|
||||||
[Bindable] private var defaultWidth:Number = 320;
|
|
||||||
[Bindable] private var defaultHeight:Number = 240;
|
|
||||||
|
|
||||||
[Bindable] public var glowColor:String = "";
|
|
||||||
[Bindable] public var glowBlurSize:Number = 0;
|
|
||||||
|
|
||||||
private var avatarWidth:Number = 320;
|
|
||||||
private var avatarHeight:Number = 240;
|
|
||||||
|
|
||||||
private var videoconfOptions:VideoConfOptions = new VideoConfOptions();
|
|
||||||
|
|
||||||
private var windowType:String = "AvatarWindowType";
|
|
||||||
|
|
||||||
override public function getWindowType():String {
|
|
||||||
return windowType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private var loader:Loader;
|
|
||||||
private var request:URLRequest;
|
|
||||||
|
|
||||||
private function loadAvatar():void {
|
|
||||||
|
|
||||||
request = new URLRequest(UsersUtil.getAvatarURL());
|
|
||||||
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadingComplete);
|
|
||||||
loader.load(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onLoadingComplete(event:Event):void {
|
|
||||||
// Save the size of the avatar image.
|
|
||||||
avatarWidth = loader.content.width;
|
|
||||||
avatarHeight = loader.content.height;
|
|
||||||
|
|
||||||
onResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onCreationComplete():void {
|
|
||||||
this.glowColor = videoconfOptions.glowColor;
|
|
||||||
this.glowBlurSize = videoconfOptions.glowBlurSize;
|
|
||||||
|
|
||||||
loader = new Loader();
|
|
||||||
_videoHolder = new UIComponent();
|
|
||||||
_videoHolder.width = avatarWidth;
|
|
||||||
_videoHolder.height = avatarHeight;
|
|
||||||
this.addChild(_videoHolder);
|
|
||||||
|
|
||||||
_videoHolder.addChild(loader);
|
|
||||||
|
|
||||||
_video = new Video();
|
|
||||||
_video.width = avatarWidth;
|
|
||||||
_video.height = avatarHeight;
|
|
||||||
this.minWidth = _minWidth;
|
|
||||||
this.minHeight = _minHeight;
|
|
||||||
maximizeRestoreBtn.visible = false;
|
|
||||||
|
|
||||||
this.showCloseButton = videoconfOptions.showCloseButton;
|
|
||||||
|
|
||||||
this.resizable = false;
|
|
||||||
setAspectRatio(avatarWidth, avatarHeight);
|
|
||||||
|
|
||||||
addEventListener(MDIWindowEvent.RESIZE_START, onResizeStart);
|
|
||||||
addEventListener(MDIWindowEvent.RESIZE_END, onResizeEnd);
|
|
||||||
addEventListener(MouseEvent.MOUSE_OVER, showButtons);
|
|
||||||
addEventListener(MouseEvent.MOUSE_OUT, hideButtons);
|
|
||||||
|
|
||||||
startPublishing();
|
|
||||||
|
|
||||||
loadAvatar();
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected function onResize():void {
|
|
||||||
|
|
||||||
super.onResize();
|
|
||||||
|
|
||||||
if (loader != null && _videoHolder != null) {
|
|
||||||
if (avatarIsSmallerThanWindow()) {
|
|
||||||
// The avatar image is smaller than the window. Just center the image.
|
|
||||||
_video.width = loader.width = avatarWidth;
|
|
||||||
_video.height = loader.height = avatarHeight;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// The avatar is bigger than the window. Fit into the window maintaining aspect ratio.
|
|
||||||
fitAvatarToWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
_video.x = loader.x = (this.width - PADDING_HORIZONTAL - loader.width) / 2;
|
|
||||||
_video.y = loader.y = (this.height - PADDING_VERTICAL - loader.height) / 2;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fitAvatarToWindow():void {
|
|
||||||
if (_videoHolder.width - PADDING_HORIZONTAL < _videoHolder.height - PADDING_VERTICAL) {
|
|
||||||
fitToHeightAndAdjustWidthToMaintainAspectRatio();
|
|
||||||
} else {
|
|
||||||
fitToWidthAndAdjustHeightToMaintainAspectRatio();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function avatarIsSmallerThanWindow():Boolean {
|
|
||||||
return (avatarWidth < _videoHolder.width - PADDING_HORIZONTAL) && (avatarHeight < _videoHolder.height - PADDING_VERTICAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fitToWidthAndAdjustHeightToMaintainAspectRatio():void {
|
|
||||||
var aspect:Number = avatarHeight / avatarWidth;
|
|
||||||
_video.width = loader.width = _videoHolder.width - PADDING_HORIZONTAL;
|
|
||||||
|
|
||||||
// Maintain aspect-ratio
|
|
||||||
_video.height = loader.height = loader.width * aspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fitToHeightAndAdjustWidthToMaintainAspectRatio():void {
|
|
||||||
var aspect:Number = avatarWidth / avatarHeight;
|
|
||||||
_video.height = loader.height = _videoHolder.height - PADDING_VERTICAL;
|
|
||||||
|
|
||||||
// Maintain aspect-ratio
|
|
||||||
_video.width = loader.width = loader.height * aspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleMadePresenterEvent(event:MadePresenterEvent):void {
|
|
||||||
LOGGER.debug("******** Avatar: HandleMadePresenter event *********");
|
|
||||||
updateControlButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleSwitchedPresenterEvent(event:SwitchedPresenterEvent):void {
|
|
||||||
LOGGER.debug("******** Avatar: handleSwitchedPresenterEvent event *********");
|
|
||||||
updateControlButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleNewRoleEvent(event:Event):void {
|
|
||||||
updateControlButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleUserVoiceMutedEvent(event:BBBEvent):void {
|
|
||||||
if (event.payload.userID == userID) {
|
|
||||||
userMuted(event.payload.muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleUserTalkingEvent(event:CoreEvent):void {
|
|
||||||
if (event.message.userID == userID) {
|
|
||||||
if (event.message.talking) {
|
|
||||||
notTalkingEffect.end();
|
|
||||||
talkingEffect.play([this]);
|
|
||||||
simulateClick();
|
|
||||||
} else {
|
|
||||||
talkingEffect.end();
|
|
||||||
notTalkingEffect.play([this]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function startPublishing():void{
|
|
||||||
this.resizable = true;
|
|
||||||
onResize();
|
|
||||||
|
|
||||||
createButtons();
|
|
||||||
addControlButtons();
|
|
||||||
updateButtonsPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
override public function close(event:MouseEvent=null):void{
|
|
||||||
closeThisWindow();
|
|
||||||
super.close(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function closeWindow(e:CloseAllWindowsEvent):void{
|
|
||||||
closeThisWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function closeThisWindow():void {
|
|
||||||
LOGGER.debug("* Closing avatar window for user [{0}] *", [userID]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
]]>
|
|
||||||
</mx:Script>
|
|
||||||
|
|
||||||
<mx:Glow id="talkingEffect" duration="500" alphaFrom="1.0" alphaTo="0.3"
|
|
||||||
blurXFrom="0.0" blurXTo="{glowBlurSize}" blurYFrom="0.0" blurYTo="{glowBlurSize}" color="{glowColor}"/>
|
|
||||||
<mx:Glow id="notTalkingEffect" duration="500" alphaFrom="0.3" alphaTo="1.0"
|
|
||||||
blurXFrom="{glowBlurSize}" blurXTo="0.0" blurYFrom="{glowBlurSize}" blurYTo="0.0" color="{glowColor}"/>
|
|
||||||
|
|
||||||
</pubVid:VideoWindowItf>
|
|
@ -33,6 +33,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
creationComplete="onCreationComplete()">
|
creationComplete="onCreationComplete()">
|
||||||
|
|
||||||
<mate:Listener type="{ShortcutEvent.FOCUS_VIDEO_WINDOW}" method="focusWindow" />
|
<mate:Listener type="{ShortcutEvent.FOCUS_VIDEO_WINDOW}" method="focusWindow" />
|
||||||
|
<mate:Listener type="{SwitchedLayoutEvent.SWITCHED_LAYOUT_EVENT}" method="onLayoutChanged" />
|
||||||
|
|
||||||
<mx:Script>
|
<mx:Script>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
@ -41,6 +42,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
import mx.core.UIComponent;
|
import mx.core.UIComponent;
|
||||||
|
|
||||||
import org.bigbluebutton.core.KeyboardUtil;
|
import org.bigbluebutton.core.KeyboardUtil;
|
||||||
|
import org.bigbluebutton.core.events.SwitchedLayoutEvent;
|
||||||
import org.bigbluebutton.main.events.ShortcutEvent;
|
import org.bigbluebutton.main.events.ShortcutEvent;
|
||||||
import org.bigbluebutton.main.views.MainCanvas;
|
import org.bigbluebutton.main.views.MainCanvas;
|
||||||
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
|
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
|
||||||
@ -50,6 +52,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
private var keyCombos:Object;
|
private var keyCombos:Object;
|
||||||
private var disp:Dispatcher = new Dispatcher();
|
private var disp:Dispatcher = new Dispatcher();
|
||||||
|
|
||||||
|
private var darkMode:Boolean;
|
||||||
|
|
||||||
private function onCreationComplete():void {
|
private function onCreationComplete():void {
|
||||||
hotkeyCapture();
|
hotkeyCapture();
|
||||||
titleBarOverlay.tabIndex = videoOptions.baseTabIndex;
|
titleBarOverlay.tabIndex = videoOptions.baseTabIndex;
|
||||||
@ -89,7 +93,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
override protected function resourcesChanged():void {
|
override protected function resourcesChanged():void {
|
||||||
super.resourcesChanged();
|
super.resourcesChanged();
|
||||||
|
if (!darkMode) {
|
||||||
this.title = ResourceUtil.getInstance().getString("bbb.videodock.title");
|
this.title = ResourceUtil.getInstance().getString("bbb.videodock.title");
|
||||||
|
} else {
|
||||||
|
this.title = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (titleBarOverlay != null) {
|
if (titleBarOverlay != null) {
|
||||||
titleBarOverlay.accessibilityName = ResourceUtil.getInstance().getString('bbb.videoDock.titleBar');
|
titleBarOverlay.accessibilityName = ResourceUtil.getInstance().getString('bbb.videoDock.titleBar');
|
||||||
@ -112,8 +120,25 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function focusWindow(e:ShortcutEvent):void {
|
private function focusWindow(e:ShortcutEvent):void {
|
||||||
|
if (this.visible) {
|
||||||
focusManager.setFocus(titleBarOverlay);
|
focusManager.setFocus(titleBarOverlay);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onLayoutChanged(e:SwitchedLayoutEvent):void {
|
||||||
|
if(e.layoutID != "bbb.layout.name.videochat"){
|
||||||
|
setStyle("styleNameFocus", "videoDockStyleFocus");
|
||||||
|
setStyle("styleNameNoFocus", "videoDockStyleNoFocus");
|
||||||
|
showControls = true;
|
||||||
|
this.title = ResourceUtil.getInstance().getString("bbb.videodock.title");
|
||||||
|
} else {
|
||||||
|
setStyle("styleNameFocus", "videoDockStyleFocusChatLayout");
|
||||||
|
setStyle("styleNameNoFocus", "videoDockStyleNoFocusChatLayout");
|
||||||
|
showControls = false;
|
||||||
|
this.title = "";
|
||||||
|
}
|
||||||
|
styleChanged("styleName");
|
||||||
|
}
|
||||||
|
|
||||||
private function remoteMinimize(e:ShortcutEvent):void {
|
private function remoteMinimize(e:ShortcutEvent):void {
|
||||||
if (!minimized) {
|
if (!minimized) {
|
||||||
|
@ -9,7 +9,6 @@ arunoda:npm@0.2.6
|
|||||||
amplify
|
amplify
|
||||||
blaze@2.1.8
|
blaze@2.1.8
|
||||||
francocatena:status
|
francocatena:status
|
||||||
mrt:external-file-loader@0.1.4
|
|
||||||
mizzao:timesync
|
mizzao:timesync
|
||||||
clinical:nightwatch
|
clinical:nightwatch
|
||||||
cfs:power-queue
|
cfs:power-queue
|
||||||
|
@ -56,7 +56,6 @@ modules@0.7.9
|
|||||||
modules-runtime@0.7.9
|
modules-runtime@0.7.9
|
||||||
mongo@1.1.15
|
mongo@1.1.15
|
||||||
mongo-id@1.0.6
|
mongo-id@1.0.6
|
||||||
mrt:external-file-loader@0.1.4
|
|
||||||
nathantreid:css-modules@2.4.0
|
nathantreid:css-modules@2.4.0
|
||||||
npm-mongo@2.2.16_1
|
npm-mongo@2.2.16_1
|
||||||
observe-sequence@1.0.15
|
observe-sequence@1.0.15
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
html {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *:before, *:after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Source Sans Pro', Arial, sans-serif;
|
|
||||||
font-size: 1rem; /* 16px */
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0,0,0,0);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden]:not([hidden="false"]) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEBUG ONLY
|
|
||||||
* {
|
|
||||||
background-color: rgba(0, 0, 0, .025) !important;
|
|
||||||
}
|
|
||||||
*/
|
|
@ -1,7 +1,55 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>BBB - HTML5 Client</title>
|
<title>BBB - HTML5 Client</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Source Sans Pro', Arial, sans-serif;
|
||||||
|
font-size: 1rem; /* 16px */
|
||||||
|
background-color: #2A2D33;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0,0,0,0);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden]:not([hidden="false"]) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="background-color: #2A2D33">
|
||||||
<div id="app" role="application"></div>
|
<div id="app" role="application"></div>
|
||||||
|
<script src="/client/lib/sip.js"></script>
|
||||||
|
<script src="/client/lib/bbb_webrtc_bridge_sip.js"></script>
|
||||||
|
<script src="/client/lib/bbblogger.js"></script>
|
||||||
|
<script src="/client/lib/jquery.json-2.4.min.js"></script>
|
||||||
|
<script src="/client/lib/jquery.FSRTC.js"></script>
|
||||||
|
<script src="/client/lib/jquery.verto.js"></script>
|
||||||
|
<script src="/client/lib/verto_extension.js"></script>
|
||||||
|
<script src="/client/lib/jquery.jsonrpcclient.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,90 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { showModal } from '/imports/ui/components/app/service';
|
import { renderRoutes } from '/imports/startup/client/routes';
|
||||||
import { renderRoutes } from '../imports/startup/client/routes.js';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import Singleton from '/imports/ui/services/storage/local.js';
|
|
||||||
import AudioModalContainer from '/imports/ui/components/audio-modal/container';
|
|
||||||
|
|
||||||
function loadUserSettings() {
|
|
||||||
const userSavedFontSize = Singleton.getItem('bbbSavedFontSizePixels');
|
|
||||||
|
|
||||||
if (userSavedFontSize) {
|
|
||||||
document.getElementsByTagName('html')[0].style.fontSize = userSavedFontSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAudio() {
|
|
||||||
const LOG_CONFIG = Meteor.settings || {};
|
|
||||||
let autoJoinAudio = LOG_CONFIG.public.app.autoJoinAudio;
|
|
||||||
|
|
||||||
if (autoJoinAudio) {
|
|
||||||
showModal( <AudioModalContainer /> );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMessages(data) {
|
|
||||||
let messages = data;
|
|
||||||
let defaultLocale = 'en';
|
|
||||||
|
|
||||||
render((
|
|
||||||
<IntlProvider locale={defaultLocale} messages={messages}>
|
|
||||||
{renderRoutes()}
|
|
||||||
</IntlProvider>
|
|
||||||
), document.getElementById('app'));
|
|
||||||
|
|
||||||
setAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to load javascript libraries from the BBB server
|
|
||||||
function loadLib(libname, success, fail) {
|
|
||||||
const successCallback = function (cb) {
|
|
||||||
console.log(`successfully loaded lib - ${this}`);
|
|
||||||
if (typeof (cb) == 'function' || cb instanceof Function) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const failCallback = function (cb, issue) {
|
|
||||||
console.error(`failed to load lib - ${this}`);
|
|
||||||
console.error(issue);
|
|
||||||
if (typeof (cb) == 'function' || cb instanceof Function) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Meteor.Loader.loadJs(`${window.location.origin}/client/lib/${libname}`,
|
|
||||||
successCallback.bind(libname, success), 10000).fail(failCallback.bind(libname, fail));
|
|
||||||
};
|
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
render(renderRoutes(), document.getElementById('app'));
|
||||||
loadLib('sip.js');
|
|
||||||
loadLib('bbb_webrtc_bridge_sip.js');
|
|
||||||
loadLib('bbblogger.js');
|
|
||||||
loadLib('jquery.json-2.4.min.js');
|
|
||||||
loadLib('jquery.FSRTC.js');
|
|
||||||
loadLib('jquery.verto.js');
|
|
||||||
loadLib('verto_extension.js');
|
|
||||||
loadLib('jquery.jsonrpcclient.js');
|
|
||||||
|
|
||||||
loadUserSettings();
|
|
||||||
|
|
||||||
let browserLanguage = navigator.language; //set this manually to force localization in a specific language
|
|
||||||
let request = new Request
|
|
||||||
(`${window.location.origin}/html5client/locale?locale=${browserLanguage}`);
|
|
||||||
|
|
||||||
fetch(request, { method: 'GET' })
|
|
||||||
.then(function (response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function (data) {
|
|
||||||
setMessages(data);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
console.log('request failed', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import Breakouts from '/imports/api/breakouts';
|
import Breakouts from '/imports/api/breakouts';
|
||||||
import Users from '/imports/api/users';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
import Logger from '/imports/startup/server/logger';
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import RedisPubSub from '/imports/startup/server/redis';
|
|
||||||
import { XMLHttpRequest } from 'xmlhttprequest';
|
|
||||||
import xml2js from 'xml2js';
|
import xml2js from 'xml2js';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
const xmlParser = new xml2js.Parser();
|
const xmlParser = new xml2js.Parser();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import Captions from '/imports/api/captions';
|
import Captions from '/imports/api/captions';
|
||||||
import Logger from '/imports/startup/server/logger';
|
import Logger from '/imports/startup/server/logger';
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
|
@ -8,11 +8,11 @@ import addChat from '/imports/api/chat/server/modifiers/addChat';
|
|||||||
export default function handleValidateAuthToken({ payload }) {
|
export default function handleValidateAuthToken({ payload }) {
|
||||||
const meetingId = payload.meeting_id;
|
const meetingId = payload.meeting_id;
|
||||||
const userId = payload.userid;
|
const userId = payload.userid;
|
||||||
const validStatus = payload.valid;
|
const validStatus = JSON.parse(payload.valid);
|
||||||
|
|
||||||
check(meetingId, String);
|
check(meetingId, String);
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
check(validStatus, String);
|
check(validStatus, Boolean);
|
||||||
|
|
||||||
const selector = {
|
const selector = {
|
||||||
meetingId,
|
meetingId,
|
||||||
@ -21,14 +21,11 @@ export default function handleValidateAuthToken({ payload }) {
|
|||||||
|
|
||||||
const User = Users.findOne(selector);
|
const User = Users.findOne(selector);
|
||||||
|
|
||||||
if (!User) {
|
// If we dont find the user on our collection is a flash user and we can skip
|
||||||
throw new Meteor.Error(
|
if (!User) return;
|
||||||
'user-not-found', `You need a valid user to be able validate the token`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (User.validated === validStatus) {
|
// User already flagged so we skip
|
||||||
return;
|
if (User.validated === validStatus) return;
|
||||||
}
|
|
||||||
|
|
||||||
const modifier = {
|
const modifier = {
|
||||||
$set: {
|
$set: {
|
||||||
|
@ -5,6 +5,7 @@ import userLogout from './methods/userLogout';
|
|||||||
import assignPresenter from './methods/assignPresenter';
|
import assignPresenter from './methods/assignPresenter';
|
||||||
import muteToggle from './methods/muteToggle';
|
import muteToggle from './methods/muteToggle';
|
||||||
import setEmojiStatus from './methods/setEmojiStatus';
|
import setEmojiStatus from './methods/setEmojiStatus';
|
||||||
|
import validateAuthToken from './methods/validateAuthToken';
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
kickUser,
|
kickUser,
|
||||||
@ -12,6 +13,7 @@ Meteor.methods({
|
|||||||
userLogout,
|
userLogout,
|
||||||
assignPresenter,
|
assignPresenter,
|
||||||
setEmojiStatus,
|
setEmojiStatus,
|
||||||
|
validateAuthToken,
|
||||||
muteUser: (...args) => muteToggle(...args, true),
|
muteUser: (...args) => muteToggle(...args, true),
|
||||||
unmuteUser: (...args) => muteToggle(...args, false),
|
unmuteUser: (...args) => muteToggle(...args, false),
|
||||||
});
|
});
|
||||||
|
@ -21,11 +21,12 @@ export default function userLeaving(credentials, userId) {
|
|||||||
check(requesterUserId, String);
|
check(requesterUserId, String);
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
|
|
||||||
const User = Users.findOne({
|
const selector = {
|
||||||
meetingId,
|
meetingId,
|
||||||
userId,
|
userId,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const User = Users.findOne(selector);
|
||||||
if (!User) {
|
if (!User) {
|
||||||
throw new Meteor.Error(
|
throw new Meteor.Error(
|
||||||
'user-not-found', `You need a valid user to be able to toggle audio`);
|
'user-not-found', `You need a valid user to be able to toggle audio`);
|
||||||
@ -39,6 +40,26 @@ export default function userLeaving(credentials, userId) {
|
|||||||
listenOnlyToggle(credentials, false);
|
listenOnlyToggle(credentials, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (User.validated) {
|
||||||
|
const modifier = {
|
||||||
|
$set: {
|
||||||
|
validated: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const cb = (err, numChanged) => {
|
||||||
|
if (err) {
|
||||||
|
return Logger.error(`Invalidating user: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numChanged) {
|
||||||
|
return Logger.info(`Invalidate user=${userId} meeting=${meetingId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Users.update(selector, modifier, cb);
|
||||||
|
}
|
||||||
|
|
||||||
let payload = {
|
let payload = {
|
||||||
meeting_id: meetingId,
|
meeting_id: meetingId,
|
||||||
userid: userId,
|
userid: userId,
|
||||||
|
@ -42,7 +42,7 @@ export default function validateAuthToken(credentials) {
|
|||||||
reply_to: `${meetingId}/${requesterUserId}`,
|
reply_to: `${meetingId}/${requesterUserId}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.verbose(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}'`);
|
Logger.info(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}'`);
|
||||||
|
|
||||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload, header);
|
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload, header);
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ export default function createDummyUser(meetingId, userId, authToken) {
|
|||||||
userId,
|
userId,
|
||||||
authToken,
|
authToken,
|
||||||
clientType: 'HTML5',
|
clientType: 'HTML5',
|
||||||
validated: false,
|
validated: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cb = (err, numChanged) => {
|
const cb = (err, numChanged) => {
|
||||||
|
@ -5,7 +5,28 @@ import Logger from '/imports/startup/server/logger';
|
|||||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||||
|
|
||||||
import userLeaving from './methods/userLeaving';
|
import userLeaving from './methods/userLeaving';
|
||||||
import validateAuthToken from './methods/validateAuthToken';
|
|
||||||
|
Meteor.publish('current-user', function (credentials) {
|
||||||
|
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||||
|
|
||||||
|
check(meetingId, String);
|
||||||
|
check(requesterUserId, String);
|
||||||
|
check(requesterToken, String);
|
||||||
|
|
||||||
|
const selector = {
|
||||||
|
meetingId,
|
||||||
|
userId: requesterUserId,
|
||||||
|
authToken: requesterToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
fields: {
|
||||||
|
user: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Users.find(selector, options);
|
||||||
|
});
|
||||||
|
|
||||||
Meteor.publish('users', function (credentials) {
|
Meteor.publish('users', function (credentials) {
|
||||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||||
@ -14,12 +35,9 @@ Meteor.publish('users', function (credentials) {
|
|||||||
check(requesterUserId, String);
|
check(requesterUserId, String);
|
||||||
check(requesterToken, String);
|
check(requesterToken, String);
|
||||||
|
|
||||||
validateAuthToken(credentials);
|
if (!isAllowedTo('subscribeUsers', credentials)) {
|
||||||
|
this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'Users'"));
|
||||||
// TODO(auth): We need to fix the Authentication flow to enable ACL
|
}
|
||||||
// if (!isAllowedTo('subscribeUsers', credentials)) {
|
|
||||||
// this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'Users'"));
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.onStop(() => {
|
this.onStop(() => {
|
||||||
userLeaving(credentials, requesterUserId);
|
userLeaving(credentials, requesterUserId);
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import en from './en.json';
|
|
||||||
import de from './de.json';
|
|
||||||
import ptBR from './pt-BR.json';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
en: en,
|
|
||||||
de: de,
|
|
||||||
'pt-BR': ptBR,
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"app.home.greeting": "Bem-vindo {name}! Sua aprensentação começará em breve...",
|
|
||||||
"app.userlist.participantsTitle": "Participantes",
|
|
||||||
"app.userlist.messagesTitle": "Mensagens",
|
|
||||||
"app.userlist.presenter": "Apresentador",
|
|
||||||
"app.userlist.you": "Você",
|
|
||||||
"app.chat.submitLabel": "Enviar Mensagem",
|
|
||||||
"app.chat.inputLabel": "Campo de mensagem para conversa {name}",
|
|
||||||
"app.chat.titlePublic": "Conversa Publíca",
|
|
||||||
"app.chat.titlePrivate": "Conversa Privada com {name}",
|
|
||||||
"app.chat.partnerDisconnected": "{name} saiu da sala",
|
|
||||||
"app.chat.moreMessages": "Mais mensagens abaixo",
|
|
||||||
"app.kickMessage": "Você foi expulso da apresentação",
|
|
||||||
"app.failedMessage": "Desculpas, estamos com problemas para se conectar ao servidor.",
|
|
||||||
"app.connectingMessage": "Conectando...",
|
|
||||||
"app.waitingMessage": "Desconectado. Tentando reconectar em {seconds} segundos..."
|
|
||||||
}
|
|
48
bigbluebutton-html5/imports/startup/client/auth.js
Normal file
48
bigbluebutton-html5/imports/startup/client/auth.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
|
||||||
|
export function joinRouteHandler(nextState, replace, callback) {
|
||||||
|
if (!nextState || !nextState.params.authToken) {
|
||||||
|
replace({ pathname: '/error/404' });
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { meetingID, userID, authToken } = nextState.params;
|
||||||
|
Auth.authenticate(meetingID, userID, authToken)
|
||||||
|
.then(() => {
|
||||||
|
replace({ pathname: '/' });
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
replace({ pathname: '/error/401' });
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function logoutRouteHandler(nextState, replace, callback) {
|
||||||
|
const { meetingID, userID, authToken } = nextState.params;
|
||||||
|
|
||||||
|
Auth.logout()
|
||||||
|
.then(logoutURL => {
|
||||||
|
window.location = logoutURL || window.location.origin;
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
console.error(reason);
|
||||||
|
replace({ pathname: '/error/500' });
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function authenticatedRouteHandler(nextState, replace, callback) {
|
||||||
|
if (Auth.loggedIn) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth.authenticate()
|
||||||
|
.then(callback)
|
||||||
|
.catch(reason => {
|
||||||
|
console.error(reason);
|
||||||
|
replace({ pathname: '/error/401' });
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
91
bigbluebutton-html5/imports/startup/client/base.js
Normal file
91
bigbluebutton-html5/imports/startup/client/base.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
|
|
||||||
|
import IntlStartup from './intl';
|
||||||
|
|
||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
|
||||||
|
import AppContainer from '/imports/ui/components/app/container';
|
||||||
|
import ErrorScreen from '/imports/ui/components/error-screen/component';
|
||||||
|
import LoadingScreen from '/imports/ui/components/loading-screen/component';
|
||||||
|
|
||||||
|
const BROWSER_LANGUAGE = window.navigator.userLanguage || window.navigator.language;
|
||||||
|
|
||||||
|
class Base extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
error: props.error || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateLoadingState = this.updateLoadingState.bind(this);
|
||||||
|
this.updateErrorState = this.updateErrorState.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(loading = false) {
|
||||||
|
this.setState({
|
||||||
|
loading,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateErrorState(error = null) {
|
||||||
|
this.setState({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderByState() {
|
||||||
|
const { updateLoadingState, updateErrorState } = this;
|
||||||
|
const stateControls = { updateLoadingState, updateErrorState };
|
||||||
|
|
||||||
|
const { loading, error } = this.state;
|
||||||
|
|
||||||
|
const { subscriptionsReady, errorCode } = this.props;
|
||||||
|
|
||||||
|
if (error || errorCode) {
|
||||||
|
return (<ErrorScreen code={errorCode}>{error}</ErrorScreen>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading || !subscriptionsReady) {
|
||||||
|
return (<LoadingScreen>{loading}</LoadingScreen>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<AppContainer {...this.props} baseControls={stateControls}/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { updateLoadingState, updateErrorState } = this;
|
||||||
|
const stateControls = { updateLoadingState, updateErrorState };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlStartup locale={BROWSER_LANGUAGE} baseControls={stateControls}>
|
||||||
|
{this.renderByState()}
|
||||||
|
</IntlStartup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUBSCRIPTIONS_NAME = [
|
||||||
|
'users', 'chat', 'cursor', 'deskshare', 'meetings',
|
||||||
|
'polls', 'presentations', 'shapes', 'slides', 'captions', 'breakouts',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default BaseContainer = createContainer(({ params }) => {
|
||||||
|
if (params.errorCode) return params;
|
||||||
|
|
||||||
|
if (!Auth.loggedIn) {
|
||||||
|
return {
|
||||||
|
errorCode: 401,
|
||||||
|
error: 'You are unauthorized to access this meeting',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = Auth.credentials;
|
||||||
|
const subscriptionsHandlers = SUBSCRIPTIONS_NAME.map(name => Meteor.subscribe(name, credentials));
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptionsReady: subscriptionsHandlers.every(handler => handler.ready()),
|
||||||
|
};
|
||||||
|
}, Base);
|
64
bigbluebutton-html5/imports/startup/client/intl.js
Normal file
64
bigbluebutton-html5/imports/startup/client/intl.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
locale: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
locale: 'en',
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntlStartup extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
messages: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fetchLocalizedMessages = this.fetchLocalizedMessages.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLocalizedMessages(locale) {
|
||||||
|
const url = `/html5client/locale?locale=${locale}`;
|
||||||
|
|
||||||
|
const { baseControls } = this.props;
|
||||||
|
|
||||||
|
baseControls.updateLoadingState(true);
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(messages => {
|
||||||
|
this.setState({ messages }, () => {
|
||||||
|
baseControls.updateLoadingState(false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
baseControls.updateErrorState(reason);
|
||||||
|
baseControls.updateLoadingState(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.fetchLocalizedMessages(this.props.locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
if (this.props.locale !== nextProps.locale) {
|
||||||
|
this.fetchLocalizedMessages(nextProps.locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={this.props.locale} messages={this.state.messages}>
|
||||||
|
{this.props.children}
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IntlStartup;
|
||||||
|
|
||||||
|
IntlStartup.propTypes = propTypes;
|
||||||
|
IntlStartup.defaultProps = defaultProps;
|
@ -2,45 +2,32 @@ import React from 'react';
|
|||||||
import { Router, Route, Redirect, IndexRoute, useRouterHistory } from 'react-router';
|
import { Router, Route, Redirect, IndexRoute, useRouterHistory } from 'react-router';
|
||||||
import { createHistory } from 'history';
|
import { createHistory } from 'history';
|
||||||
|
|
||||||
// route components
|
import { joinRouteHandler, logoutRouteHandler, authenticatedRouteHandler } from './auth';
|
||||||
import AppContainer from '/imports/ui/components/app/container';
|
import Base from './base';
|
||||||
import { subscribeToCollections, setCredentials } from '/imports/ui/components/app/service';
|
|
||||||
|
|
||||||
|
import LoadingScreen from '/imports/ui/components/loading-screen/component';
|
||||||
import ChatContainer from '/imports/ui/components/chat/container';
|
import ChatContainer from '/imports/ui/components/chat/container';
|
||||||
import UserListContainer from '/imports/ui/components/user-list/container';
|
import UserListContainer from '/imports/ui/components/user-list/container';
|
||||||
|
|
||||||
const browserHistory = useRouterHistory(createHistory)({
|
const browserHistory = useRouterHistory(createHistory)({
|
||||||
// Name displayed in the brower URL
|
|
||||||
basename: Meteor.settings.public.app.basename,
|
basename: Meteor.settings.public.app.basename,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const renderRoutes = () => (
|
export const renderRoutes = () => (
|
||||||
<Router history={browserHistory}>
|
<Router history={browserHistory}>
|
||||||
<Route path="/join/:meetingID/:userID/:authToken" onEnter={setCredentials} />
|
<Route path="/logout" onEnter={logoutRouteHandler} />
|
||||||
<Route path="/" onEnter={() => {
|
<Route path="/join/:meetingID/:userID/:authToken"
|
||||||
subscribeToCollections();
|
component={LoadingScreen} onEnter={joinRouteHandler} />
|
||||||
}}
|
<Route path="/" component={Base} onEnter={authenticatedRouteHandler} >
|
||||||
|
|
||||||
getComponent={(nextState, cb) => {
|
|
||||||
subscribeToCollections(() => cb(null, AppContainer));
|
|
||||||
}}>
|
|
||||||
<IndexRoute components={{}} />
|
<IndexRoute components={{}} />
|
||||||
|
<Route name="users" path="users" components={{ userList: UserListContainer }} />
|
||||||
<Route name="users" path="users" getComponents={(nextState, cb) => {
|
<Route name="chat" path="users/chat/:chatID" components={{
|
||||||
subscribeToCollections(() => cb(null, {
|
|
||||||
userList: UserListContainer,
|
|
||||||
}));
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<Route name="chat" path="users/chat/:chatID" getComponents={(nextState, cb) => {
|
|
||||||
subscribeToCollections(() => cb(null, {
|
|
||||||
userList: UserListContainer,
|
userList: UserListContainer,
|
||||||
chat: ChatContainer,
|
chat: ChatContainer,
|
||||||
}));
|
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
<Redirect from="users/chat" to="/users/chat/public" />
|
<Redirect from="users/chat" to="/users/chat/public" />
|
||||||
</Route>
|
</Route>
|
||||||
<Redirect from="*" to="/" />
|
<Route name="error" path="/error/:errorCode" component={Base}/>
|
||||||
|
<Redirect from="*" to="/error/404" />
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import Locales from '/imports/locales';
|
import _ from 'lodash';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import Redis from './redis';
|
import Redis from './redis';
|
||||||
|
|
||||||
@ -17,22 +17,32 @@ WebApp.connectHandlers.use('/check', (req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
WebApp.connectHandlers.use('/locale', (req, res) => {
|
WebApp.connectHandlers.use('/locale', (req, res) => {
|
||||||
let defaultLocale = 'en';
|
const APP_CONFIG = Meteor.settings.public.app;
|
||||||
let [locale, region] = req.query.locale.split('-');
|
|
||||||
|
|
||||||
const defaultMessages = Locales[defaultLocale];
|
let defaultLocale = APP_CONFIG.defaultLocale;
|
||||||
|
let localeRegion = _.snakeCase(req.query.locale).split('_');
|
||||||
|
let messages = {};
|
||||||
|
|
||||||
let messages = Object.assign(
|
let locales = [defaultLocale, localeRegion[0]];
|
||||||
{},
|
|
||||||
defaultMessages,
|
if (localeRegion.length > 1) {
|
||||||
Locales[locale],
|
locales.push(`${localeRegion[0]}_${localeRegion[1]}`);
|
||||||
Locales[`${locale}-${region}`],
|
}
|
||||||
);
|
|
||||||
|
locales.forEach(locale => {
|
||||||
|
try {
|
||||||
|
const data = Assets.getText(`locales/${locale}.json`);
|
||||||
|
messages = Object.assign(messages, JSON.parse(data));
|
||||||
|
} catch (e) {
|
||||||
|
// console.error(e);
|
||||||
|
// We dont really care about those errors since they will be a parse error
|
||||||
|
// or a file not found which is ok
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
res.writeHead(200);
|
res.writeHead(200);
|
||||||
res.end(JSON.stringify(messages));
|
res.end(JSON.stringify(messages));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const eventEmitter = Redis.emitter;
|
export const eventEmitter = Redis.emitter;
|
||||||
|
@ -18,17 +18,17 @@ const intlMessages = defineMessages({
|
|||||||
class JoinAudioOptions extends React.Component {
|
class JoinAudioOptions extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
close,
|
|
||||||
intl,
|
intl,
|
||||||
isInAudio,
|
isInAudio,
|
||||||
isInListenOnly,
|
isInListenOnly,
|
||||||
open,
|
handleJoinAudio,
|
||||||
|
handleCloseAudio,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (isInAudio || isInListenOnly) {
|
if (isInAudio || isInListenOnly) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={close}
|
onClick={handleCloseAudio}
|
||||||
label={intl.formatMessage(intlMessages.leaveAudio)}
|
label={intl.formatMessage(intlMessages.leaveAudio)}
|
||||||
color={'danger'}
|
color={'danger'}
|
||||||
icon={'audio_off'}
|
icon={'audio_off'}
|
||||||
@ -40,7 +40,7 @@ class JoinAudioOptions extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={open}
|
onClick={handleJoinAudio}
|
||||||
label={intl.formatMessage(intlMessages.joinAudio)}
|
label={intl.formatMessage(intlMessages.joinAudio)}
|
||||||
color={'primary'}
|
color={'primary'}
|
||||||
icon={'audio_on'}
|
icon={'audio_on'}
|
||||||
|
@ -20,7 +20,7 @@ export default createContainer((params) => {
|
|||||||
return {
|
return {
|
||||||
isInAudio: user.voiceUser.joined,
|
isInAudio: user.voiceUser.joined,
|
||||||
isInListenOnly: user.listenOnly,
|
isInListenOnly: user.listenOnly,
|
||||||
open: params.open,
|
handleJoinAudio: params.handleJoinAudio,
|
||||||
close: params.close,
|
handleCloseAudio: params.handleCloseAudio,
|
||||||
};
|
};
|
||||||
}, JoinAudioOptionsContainer);
|
}, JoinAudioOptionsContainer);
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { showModal } from '/imports/ui/components/app/service';
|
|
||||||
import Audio from '/imports/ui/components/audio-modal/component';
|
|
||||||
import Button from '/imports/ui/components/button/component';
|
import Button from '/imports/ui/components/button/component';
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
import EmojiContainer from './emoji-menu/container';
|
import EmojiContainer from './emoji-menu/container';
|
||||||
import ActionsDropdown from './actions-dropdown/component';
|
import ActionsDropdown from './actions-dropdown/component';
|
||||||
import Auth from '/imports/ui/services/auth/index';
|
|
||||||
import Users from '/imports/api/users/index';
|
|
||||||
import JoinAudioOptionsContainer from './audio-menu/container';
|
import JoinAudioOptionsContainer from './audio-menu/container';
|
||||||
import MuteAudioContainer from './mute-button/container';
|
import MuteAudioContainer from './mute-button/container';
|
||||||
import { exitAudio } from '/imports/api/phone';
|
|
||||||
import JoinVideo from './video-button/component';
|
import JoinVideo from './video-button/component';
|
||||||
|
|
||||||
export default class ActionsBar extends Component {
|
export default class ActionsBar extends Component {
|
||||||
@ -17,10 +12,6 @@ export default class ActionsBar extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
openJoinAudio() {
|
|
||||||
return showModal(<Audio handleJoinListenOnly={this.props.handleJoinListenOnly} />)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderForPresenter() {
|
renderForPresenter() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.actionsbar}>
|
<div className={styles.actionsbar}>
|
||||||
@ -30,14 +21,15 @@ export default class ActionsBar extends Component {
|
|||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<MuteAudioContainer />
|
<MuteAudioContainer />
|
||||||
<JoinAudioOptionsContainer
|
<JoinAudioOptionsContainer
|
||||||
open={this.openJoinAudio.bind(this)}
|
handleJoinAudio={this.props.handleOpenJoinAudio}
|
||||||
close={() => {exitAudio();}}
|
handleCloseAudio={this.props.handleExitAudio}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{/*<JoinVideo />*/}
|
{/*<JoinVideo />*/}
|
||||||
<EmojiContainer />
|
<EmojiContainer />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.right}>
|
<div className={styles.hidden}>
|
||||||
|
<ActionsDropdown />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -49,15 +41,13 @@ export default class ActionsBar extends Component {
|
|||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<MuteAudioContainer />
|
<MuteAudioContainer />
|
||||||
<JoinAudioOptionsContainer
|
<JoinAudioOptionsContainer
|
||||||
open={this.openJoinAudio.bind(this)}
|
handleJoinAudio={this.props.handleOpenJoinAudio}
|
||||||
close={() => {exitAudio();}}
|
handleCloseAudio={this.props.handleExitAudio}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{/*<JoinVideo />*/}
|
{/*<JoinVideo />*/}
|
||||||
<EmojiContainer />
|
<EmojiContainer />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.right}>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { createContainer } from 'meteor/react-meteor-data';
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
import ActionsBar from './component';
|
import ActionsBar from './component';
|
||||||
import Service from './service';
|
import Service from './service';
|
||||||
import { joinListenOnly } from '/imports/api/phone';
|
|
||||||
|
|
||||||
class ActionsBarContainer extends Component {
|
class ActionsBarContainer extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -10,11 +9,8 @@ class ActionsBarContainer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const handleJoinListenOnly = () => joinListenOnly();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionsBar
|
<ActionsBar
|
||||||
handleJoinListenOnly={handleJoinListenOnly}
|
|
||||||
{...this.props}>
|
{...this.props}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</ActionsBar>
|
</ActionsBar>
|
||||||
@ -23,6 +19,13 @@ class ActionsBarContainer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default createContainer(() => {
|
export default createContainer(() => {
|
||||||
let data = Service.isUserPresenter();
|
const isPresenter = Service.isUserPresenter();
|
||||||
return data;
|
const handleExitAudio = () => Service.handleExitAudio();
|
||||||
|
const handleOpenJoinAudio = () => Service.handleJoinAudio();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isUserPresenter: isPresenter,
|
||||||
|
handleExitAudio: handleExitAudio,
|
||||||
|
handleOpenJoinAudio: handleOpenJoinAudio,
|
||||||
|
};
|
||||||
}, ActionsBarContainer);
|
}, ActionsBarContainer);
|
||||||
|
@ -5,13 +5,13 @@ import { callServer } from '/imports/ui/services/api/index.js';
|
|||||||
let getEmojiData = () => {
|
let getEmojiData = () => {
|
||||||
|
|
||||||
// Get userId and meetingId
|
// Get userId and meetingId
|
||||||
const credentials = Auth.getCredentials();
|
const credentials = Auth.credentials;
|
||||||
const { requesterUserId: userId, meetingId } = credentials;
|
const { requesterUserId: userId, meetingId } = credentials;
|
||||||
|
|
||||||
// Find the Emoji Status of this specific meeting and userid
|
// Find the Emoji Status of this specific meeting and userid
|
||||||
const userEmojiStatus = Users.findOne({
|
const userEmojiStatus = Users.findOne({
|
||||||
meetingId: meetingId,
|
meetingId: Auth.meetingID,
|
||||||
userId: userId,
|
userId: Auth.userID,
|
||||||
}).user.emoji_status;
|
}).user.emoji_status;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -6,10 +6,13 @@ export default class MuteAudio extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isInAudio, isMuted, callback, isTalking} = this.props;
|
const { isInAudio, isMuted, callback, isTalking} = this.props;
|
||||||
|
|
||||||
|
if (!isInAudio) return null;
|
||||||
|
|
||||||
let label = !isMuted ? 'Mute' : 'Unmute';
|
let label = !isMuted ? 'Mute' : 'Unmute';
|
||||||
let icon = !isMuted ? 'unmute' : 'mute';
|
let icon = !isMuted ? 'unmute' : 'mute';
|
||||||
let className = !isInAudio ? styles.invisible : null;
|
|
||||||
let tabIndex = !isInAudio ? -1 : 0;
|
let tabIndex = !isInAudio ? -1 : 0;
|
||||||
|
let className = null;
|
||||||
|
|
||||||
if (isInAudio && isTalking) {
|
if (isInAudio && isTalking) {
|
||||||
className = styles.circleGlow;
|
className = styles.circleGlow;
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
import AuthSingleton from '/imports/ui/services/auth/index.js';
|
import AuthSingleton from '/imports/ui/services/auth/index.js';
|
||||||
import Users from '/imports/api/users';
|
import Users from '/imports/api/users';
|
||||||
|
import { joinListenOnly } from '/imports/api/phone';
|
||||||
|
import { showModal } from '/imports/ui/components/app/service';
|
||||||
|
import { exitAudio } from '/imports/api/phone';
|
||||||
|
import Audio from '/imports/ui/components/audio-modal/component';
|
||||||
|
|
||||||
let isUserPresenter = () => {
|
let isUserPresenter = () => {
|
||||||
|
|
||||||
// check if user is a presenter
|
// check if user is a presenter
|
||||||
let isPresenter = Users.findOne({
|
let isPresenter = Users.findOne({
|
||||||
userId: AuthSingleton.getCredentials().requesterUserId,
|
userId: AuthSingleton.userID,
|
||||||
}).user.presenter;
|
}).user.presenter;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -13,6 +18,17 @@ let isUserPresenter = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExitAudio = () => {
|
||||||
|
return exitAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleJoinAudio = () => {
|
||||||
|
const handleJoinListenOnly = () => joinListenOnly();
|
||||||
|
return showModal(<Audio handleJoinListenOnly={handleJoinListenOnly} />);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isUserPresenter,
|
isUserPresenter,
|
||||||
|
handleJoinAudio,
|
||||||
|
handleExitAudio,
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
.left,
|
.left,
|
||||||
.right,
|
.right,
|
||||||
.center {
|
.center,
|
||||||
|
.hidden {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -19,7 +20,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left,
|
.left,
|
||||||
.right {
|
.right,
|
||||||
|
.hidden {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +29,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible {
|
.hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,54 +1,49 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import _ from 'lodash';
|
||||||
import LoadingScreen from '../loading-screen/component';
|
|
||||||
import KickedScreen from '../kicked-screen/component';
|
|
||||||
|
|
||||||
import NotificationsBarContainer from '../notifications-bar/container';
|
import NotificationsBarContainer from '../notifications-bar/container';
|
||||||
import AudioNotificationContainer from '../audio-notification/container';
|
import AudioNotificationContainer from '../audio-notification/container';
|
||||||
|
import ChatNotificationContainer from '../chat/notification/container';
|
||||||
import LocalStorage from '/imports/ui/services/storage/local.js';
|
|
||||||
|
|
||||||
import Button from '../button/component';
|
import Button from '../button/component';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
init: PropTypes.func.isRequired,
|
||||||
|
fontSize: PropTypes.string,
|
||||||
navbar: PropTypes.element,
|
navbar: PropTypes.element,
|
||||||
sidebar: PropTypes.element,
|
sidebar: PropTypes.element,
|
||||||
sidebarRight: PropTypes.element,
|
|
||||||
media: PropTypes.element,
|
media: PropTypes.element,
|
||||||
actionsbar: PropTypes.element,
|
actionsbar: PropTypes.element,
|
||||||
captions: PropTypes.element,
|
|
||||||
modal: PropTypes.element,
|
modal: PropTypes.element,
|
||||||
unreadMessageCount: PropTypes.array,
|
};
|
||||||
openChats: PropTypes.array,
|
|
||||||
|
const defaultProps = {
|
||||||
|
fontSize: '16px',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
compactUserList: false, //TODO: Change this on userlist resize (?)
|
compactUserList: false, //TODO: Change this on userlist resize (?)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setDefaultSettings = props.setDefaultSettings;
|
props.init.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHtmlFontSize(size) {
|
|
||||||
document.getElementsByTagName('html')[0].style.fontSize = size;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setDefaultSettings();
|
document.getElementsByTagName('html')[0].style.fontSize = this.props.fontSize;
|
||||||
this.setHtmlFontSize(this.props.fontSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNavBar() {
|
renderNavBar() {
|
||||||
const { navbar } = this.props;
|
const { navbar } = this.props;
|
||||||
|
|
||||||
if (navbar) {
|
if (!navbar) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={styles.navbar}>
|
<header className={styles.navbar}>
|
||||||
{navbar}
|
{navbar}
|
||||||
@ -56,13 +51,11 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSidebar() {
|
renderSidebar() {
|
||||||
const { sidebar } = this.props;
|
const { sidebar } = this.props;
|
||||||
|
|
||||||
if (sidebar) {
|
if (!sidebar) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={styles.sidebar}>
|
<aside className={styles.sidebar}>
|
||||||
{sidebar}
|
{sidebar}
|
||||||
@ -70,16 +63,14 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUserList() {
|
renderUserList() {
|
||||||
let { userList } = this.props;
|
let { userList } = this.props;
|
||||||
const { compactUserList } = this.state;
|
const { compactUserList } = this.state;
|
||||||
|
|
||||||
|
if (!userList) return;
|
||||||
|
|
||||||
let userListStyle = {};
|
let userListStyle = {};
|
||||||
userListStyle[styles.compact] = compactUserList;
|
userListStyle[styles.compact] = compactUserList;
|
||||||
if (userList) {
|
|
||||||
userList = React.cloneElement(userList, {
|
userList = React.cloneElement(userList, {
|
||||||
compact: compactUserList,
|
compact: compactUserList,
|
||||||
});
|
});
|
||||||
@ -91,13 +82,11 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderChat() {
|
renderChat() {
|
||||||
const { chat } = this.props;
|
const { chat } = this.props;
|
||||||
|
|
||||||
if (chat) {
|
if (!chat) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.chat} role="log">
|
<section className={styles.chat} role="log">
|
||||||
{chat}
|
{chat}
|
||||||
@ -105,13 +94,11 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMedia() {
|
renderMedia() {
|
||||||
const { media } = this.props;
|
const { media } = this.props;
|
||||||
|
|
||||||
if (media) {
|
if (!media) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.media}>
|
<section className={styles.media}>
|
||||||
{media}
|
{media}
|
||||||
@ -119,24 +106,11 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderClosedCaptions() {
|
|
||||||
const { captions } = this.props;
|
|
||||||
if (captions && this.props.getCaptionsStatus()) {
|
|
||||||
return (
|
|
||||||
<section className={styles.closedCaptions}>
|
|
||||||
{captions}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderActionsBar() {
|
renderActionsBar() {
|
||||||
const { actionsbar } = this.props;
|
const { actionsbar } = this.props;
|
||||||
|
|
||||||
if (actionsbar) {
|
if (!actionsbar) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.actionsbar}>
|
<section className={styles.actionsbar}>
|
||||||
{actionsbar}
|
{actionsbar}
|
||||||
@ -144,71 +118,8 @@ export default class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAudioElement() {
|
|
||||||
return (
|
|
||||||
<audio id="remote-media" autoPlay="autoplay"></audio>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderModal() {
|
|
||||||
const { modal } = this.props;
|
|
||||||
|
|
||||||
if (modal) {
|
|
||||||
return (<div>{modal}</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
playSoundForUnreadMessages() {
|
|
||||||
const snd = new Audio('/html5client/resources/sounds/notify.mp3');
|
|
||||||
snd.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
|
|
||||||
let { unreadMessageCount, openChats, openChat } = this.props;
|
|
||||||
|
|
||||||
unreadMessageCount.forEach((chat, i) => {
|
|
||||||
// When starting the new chat, if prevProps is undefined or null, it is assigned 0.
|
|
||||||
if (!prevProps.unreadMessageCount[i]) {
|
|
||||||
prevProps.unreadMessageCount[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare openChats(chatID) to chatID of currently opened chat room
|
|
||||||
if (openChats[i] !== openChat) {
|
|
||||||
let shouldPlaySound = this.props.applicationSettings.chatAudioNotifications;
|
|
||||||
|
|
||||||
if (shouldPlaySound && chat > prevProps.unreadMessageCount[i]) {
|
|
||||||
this.playSoundForUnreadMessages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.wasKicked) {
|
const { modal, params } = this.props;
|
||||||
return (
|
|
||||||
<KickedScreen>
|
|
||||||
<FormattedMessage
|
|
||||||
id="app.kickMessage"
|
|
||||||
description="Message when the user is kicked out of the meeting"
|
|
||||||
defaultMessage="You have been kicked out of the meeting"
|
|
||||||
/>
|
|
||||||
<br/><br/>
|
|
||||||
<Button
|
|
||||||
label={'OK'}
|
|
||||||
onClick={this.props.redirectToLogoutUrl}/>
|
|
||||||
</KickedScreen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.isLoading) {
|
|
||||||
return <LoadingScreen/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
@ -223,13 +134,14 @@ export default class App extends Component {
|
|||||||
{this.renderActionsBar()}
|
{this.renderActionsBar()}
|
||||||
</div>
|
</div>
|
||||||
{this.renderSidebar()}
|
{this.renderSidebar()}
|
||||||
{this.renderClosedCaptions()}
|
|
||||||
</section>
|
</section>
|
||||||
{this.renderAudioElement()}
|
{modal}
|
||||||
{this.renderModal()}
|
<audio id="remote-media" autoPlay="autoplay"></audio>
|
||||||
|
<ChatNotificationContainer currentChatID={params.chatID} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
App.propTypes = propTypes;
|
App.propTypes = propTypes;
|
||||||
|
App.defaultProps = defaultProps;
|
||||||
|
@ -1,32 +1,42 @@
|
|||||||
import React, { Component, PropTypes, cloneElement } from 'react';
|
import React, { Component, PropTypes, cloneElement } from 'react';
|
||||||
import { createContainer } from 'meteor/react-meteor-data';
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
import App from './component';
|
import { withRouter } from 'react-router';
|
||||||
import {
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
subscribeForData,
|
|
||||||
wasUserKicked,
|
|
||||||
redirectToLogoutUrl,
|
|
||||||
getModal,
|
|
||||||
getCaptionsStatus,
|
|
||||||
getFontSize,
|
|
||||||
} from './service';
|
|
||||||
import { setDefaultSettings, getSettingsFor } from '/imports/ui/components/settings/service';
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
getModal,
|
||||||
|
showModal,
|
||||||
|
getFontSize,
|
||||||
|
getCaptionsStatus,
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
import { setDefaultSettings } from '../settings/service';
|
||||||
|
|
||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
import Users from '/imports/api/users';
|
||||||
|
import Breakouts from '/imports/api/breakouts';
|
||||||
|
|
||||||
|
import App from './component';
|
||||||
import NavBarContainer from '../nav-bar/container';
|
import NavBarContainer from '../nav-bar/container';
|
||||||
import ActionsBarContainer from '../actions-bar/container';
|
import ActionsBarContainer from '../actions-bar/container';
|
||||||
import MediaContainer from '../media/container';
|
import MediaContainer from '../media/container';
|
||||||
import ClosedCaptionsContainer from '../closed-captions/container';
|
import AudioModalContainer from '../audio-modal/container';
|
||||||
import UserListService from '../user-list/service';
|
import ClosedCaptionsContainer from '/imports/ui/components/closed-captions/container';
|
||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
navbar: <NavBarContainer />,
|
navbar: <NavBarContainer />,
|
||||||
actionsbar: <ActionsBarContainer />,
|
actionsbar: <ActionsBarContainer />,
|
||||||
media: <MediaContainer />,
|
media: <MediaContainer />,
|
||||||
|
|
||||||
//CCs UI is commented till the next pull request
|
|
||||||
captions: <ClosedCaptionsContainer />,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
kickedMessage: {
|
||||||
|
id: 'app.error.kicked',
|
||||||
|
description: 'Message when the user is kicked out of the meeting',
|
||||||
|
defaultMessage: 'You have been kicked out of the meeting',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
class AppContainer extends Component {
|
class AppContainer extends Component {
|
||||||
render() {
|
render() {
|
||||||
// inject location on the navbar container
|
// inject location on the navbar container
|
||||||
@ -38,52 +48,44 @@ class AppContainer extends Component {
|
|||||||
</App>
|
</App>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let loading = true;
|
|
||||||
const loadingDep = new Tracker.Dependency;
|
|
||||||
|
|
||||||
const getLoading = () => {
|
|
||||||
loadingDep.depend();
|
|
||||||
return loading;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLoading = (val) => {
|
const APP_CONFIG = Meteor.settings.public.app;
|
||||||
if (val !== loading) {
|
|
||||||
loading = val;
|
const init = () => {
|
||||||
loadingDep.changed();
|
setDefaultSettings();
|
||||||
|
if (APP_CONFIG.autoJoinAudio) {
|
||||||
|
showModal(<AudioModalContainer />);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkUnreadMessages = () => {
|
export default withRouter(injectIntl(createContainer(({ router, intl, baseControls }) => {
|
||||||
return UserListService.getOpenChats().map(chat=> chat.unreadCounter)
|
// Check if user is kicked out of the session
|
||||||
.filter(userID => userID !== Auth.userID);
|
Users.find({ userId: Auth.userID }).observeChanges({
|
||||||
};
|
removed() {
|
||||||
|
Auth.clearCredentials()
|
||||||
const openChats = (chatID) => {
|
|
||||||
// get currently opened chatID
|
|
||||||
return UserListService.getOpenChats(chatID).map(chat => chat.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createContainer(({ params }) => {
|
|
||||||
Promise.all(subscribeForData())
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setLoading(false);
|
router.push('/error/403');
|
||||||
|
baseControls.updateErrorState(
|
||||||
|
intl.formatMessage(intlMessages.kickedMessage),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the widow when the current breakout room ends
|
||||||
|
Breakouts.find({ breakoutMeetingId: Auth.meetingID }).observeChanges({
|
||||||
|
removed(old) {
|
||||||
|
Auth.clearCredentials().then(window.close);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wasKicked: wasUserKicked(),
|
init,
|
||||||
isLoading: getLoading(),
|
sidebar: getCaptionsStatus() ? <ClosedCaptionsContainer /> : null,
|
||||||
modal: getModal(),
|
modal: getModal(),
|
||||||
unreadMessageCount: checkUnreadMessages(),
|
|
||||||
openChats: openChats(params.chatID),
|
|
||||||
openChat: params.chatID,
|
|
||||||
getCaptionsStatus,
|
|
||||||
redirectToLogoutUrl,
|
|
||||||
setDefaultSettings,
|
|
||||||
fontSize: getFontSize(),
|
fontSize: getFontSize(),
|
||||||
applicationSettings: getSettingsFor('application'),
|
|
||||||
};
|
};
|
||||||
}, AppContainer);
|
}, AppContainer)));
|
||||||
|
|
||||||
AppContainer.defaultProps = defaultProps;
|
AppContainer.defaultProps = defaultProps;
|
||||||
|
@ -1,118 +1,20 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
import Users from '/imports/api/users';
|
|
||||||
import Breakouts from '/imports/api/breakouts';
|
import Breakouts from '/imports/api/breakouts';
|
||||||
import Storage from '/imports/ui/services/storage/session';
|
|
||||||
import SettingsService from '/imports/ui/components/settings/service';
|
import SettingsService from '/imports/ui/components/settings/service';
|
||||||
|
|
||||||
function setCredentials(nextState, replace) {
|
let currentModal = {
|
||||||
if (nextState && nextState.params.authToken) {
|
component: null,
|
||||||
const { meetingID, userID, authToken } = nextState.params;
|
tracker: new Tracker.Dependency,
|
||||||
Auth.setCredentials(meetingID, userID, authToken);
|
|
||||||
replace({
|
|
||||||
pathname: '/',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let dataSubscriptions = null;
|
|
||||||
function subscribeForData() {
|
|
||||||
if (dataSubscriptions) {
|
|
||||||
return dataSubscriptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subNames = [
|
|
||||||
'users', 'chat', 'cursor', 'deskshare', 'meetings',
|
|
||||||
'polls', 'presentations', 'shapes', 'slides', 'captions', 'breakouts',
|
|
||||||
];
|
|
||||||
|
|
||||||
let subs = [];
|
|
||||||
subNames.forEach(name => subs.push(subscribeFor(name)));
|
|
||||||
|
|
||||||
dataSubscriptions = subs;
|
|
||||||
return subs;
|
|
||||||
};
|
|
||||||
|
|
||||||
function subscribeFor(collectionName) {
|
|
||||||
const credentials = Auth.getCredentials();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Meteor.subscribe(collectionName, credentials, {
|
|
||||||
onReady: (...args) => resolve(...args),
|
|
||||||
onStop: (...args) => reject(...args),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function subscribeToCollections(cb) {
|
|
||||||
subscribeFor('users')
|
|
||||||
.then(() => {
|
|
||||||
observeUserKick();
|
|
||||||
return Promise.all(subscribeForData())
|
|
||||||
.then(() => {
|
|
||||||
observeBreakoutEnd();
|
|
||||||
if (cb) {
|
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(redirectToLogoutUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
function redirectToLogoutUrl(reason) {
|
|
||||||
console.error(reason);
|
|
||||||
console.log('Redirecting user to the logoutURL...');
|
|
||||||
document.location.href = Auth.logoutURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
let wasKicked = false;
|
|
||||||
const wasKickedDep = new Tracker.Dependency;
|
|
||||||
|
|
||||||
function observeUserKick() {
|
|
||||||
Users.find().observe({
|
|
||||||
removed(old) {
|
|
||||||
if (old.userId === Auth.userID) {
|
|
||||||
Auth.clearCredentials(() => {
|
|
||||||
wasKicked = true;
|
|
||||||
wasKickedDep.changed();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function observeBreakoutEnd() {
|
|
||||||
Breakouts.find().observe({
|
|
||||||
removed(old) {
|
|
||||||
if (old.breakoutMeetingId === Auth.meetingID) {
|
|
||||||
// The breakout room expired. Closing the browser tab to return to the main room
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function meetingIsBreakout() {
|
|
||||||
const breakouts = Breakouts.find().fetch();
|
|
||||||
return (breakouts && breakouts.some(b => b.breakoutMeetingId === Auth.meetingID));
|
|
||||||
}
|
|
||||||
|
|
||||||
function wasUserKicked() {
|
|
||||||
wasKickedDep.depend();
|
|
||||||
return wasKicked;
|
|
||||||
}
|
|
||||||
|
|
||||||
let modal = null;
|
|
||||||
const modalDep = new Tracker.Dependency;
|
|
||||||
|
|
||||||
const getModal = () => {
|
const getModal = () => {
|
||||||
modalDep.depend();
|
currentModal.tracker.depend();
|
||||||
return modal;
|
return currentModal.component;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showModal = (val) => {
|
const showModal = (component) => {
|
||||||
if (val !== modal) {
|
if (currentModal.component !== component) {
|
||||||
modal = val;
|
currentModal.component = component;
|
||||||
modalDep.changed();
|
currentModal.tracker.changed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -121,22 +23,21 @@ const clearModal = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getCaptionsStatus = () => {
|
const getCaptionsStatus = () => {
|
||||||
const settings = Storage.getItem('settings_cc');
|
const settings = SettingsService.getSettingsFor('cc');
|
||||||
return settings ? settings.closedCaptions : false;
|
return settings ? settings.closedCaptions : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFontSize = () => {
|
const getFontSize = () => {
|
||||||
const settings = SettingsService.getSettingsFor('application');
|
const settings = SettingsService.getSettingsFor('application');
|
||||||
return settings ? settings.fontSize : '14px';
|
return settings ? settings.fontSize : '16px';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function meetingIsBreakout() {
|
||||||
|
const breakouts = Breakouts.find().fetch();
|
||||||
|
return (breakouts && breakouts.some(b => b.breakoutMeetingId === Auth.meetingID));
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
subscribeForData,
|
|
||||||
setCredentials,
|
|
||||||
subscribeFor,
|
|
||||||
subscribeToCollections,
|
|
||||||
wasUserKicked,
|
|
||||||
redirectToLogoutUrl,
|
|
||||||
getModal,
|
getModal,
|
||||||
showModal,
|
showModal,
|
||||||
clearModal,
|
clearModal,
|
||||||
|
@ -8,6 +8,7 @@ import DeviceSelector from '/imports/ui/components/audio/device-selector/compone
|
|||||||
import AudioStreamVolume from '/imports/ui/components/audio/audio-stream-volume/component';
|
import AudioStreamVolume from '/imports/ui/components/audio/audio-stream-volume/component';
|
||||||
import EnterAudioContainer from '/imports/ui/components/enter-audio/container';
|
import EnterAudioContainer from '/imports/ui/components/enter-audio/container';
|
||||||
import AudioTestContainer from '/imports/ui/components/audio-test/container';
|
import AudioTestContainer from '/imports/ui/components/audio-test/container';
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
class AudioSettings extends React.Component {
|
class AudioSettings extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -20,7 +21,7 @@ class AudioSettings extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
inputDeviceId: undefined,
|
inputDeviceId: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
chooseAudio() {
|
chooseAudio() {
|
||||||
@ -30,7 +31,7 @@ class AudioSettings extends React.Component {
|
|||||||
handleInputChange(deviceId) {
|
handleInputChange(deviceId) {
|
||||||
console.log(`INPUT DEVICE CHANGED: ${deviceId}`);
|
console.log(`INPUT DEVICE CHANGED: ${deviceId}`);
|
||||||
this.setState({
|
this.setState({
|
||||||
inputDeviceId: deviceId
|
inputDeviceId: deviceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ class AudioSettings extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.center}>
|
<div className={styles.topRow}>
|
||||||
<Button className={styles.backBtn}
|
<Button className={styles.backBtn}
|
||||||
label={intl.formatMessage(intlMessages.backLabel)}
|
label={intl.formatMessage(intlMessages.backLabel)}
|
||||||
icon={'left_arrow'}
|
icon={'left_arrow'}
|
||||||
@ -59,47 +60,69 @@ class AudioSettings extends React.Component {
|
|||||||
ghost={true}
|
ghost={true}
|
||||||
onClick={this.chooseAudio}
|
onClick={this.chooseAudio}
|
||||||
/>
|
/>
|
||||||
<div className={styles.title}>
|
<div className={cx(styles.title, styles.chooseAudio)}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="app.audio.audioSettings.titleLabel"
|
id="app.audio.audioSettings.titleLabel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.form}>
|
||||||
|
|
||||||
|
<div className={styles.row}>
|
||||||
<div className={styles.audioNote}>
|
<div className={styles.audioNote}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="app.audio.audioSettings.descriptionLabel"
|
id="app.audio.audioSettings.descriptionLabel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.containerLeftHalfContent}>
|
</div>
|
||||||
<span className={styles.heading}>
|
|
||||||
<FormattedMessage
|
<div className={styles.row}>
|
||||||
id="app.audio.audioSettings.microphoneSourceLabel"
|
<div className={styles.col}>
|
||||||
/>
|
<div className={styles.formElement}>
|
||||||
</span>
|
<label className={cx(styles.label, styles.labelSmall)}>
|
||||||
|
Microphone source
|
||||||
|
</label>
|
||||||
<DeviceSelector
|
<DeviceSelector
|
||||||
className={styles.item}
|
value={this.state.inputDeviceId}
|
||||||
|
className={styles.select}
|
||||||
kind="audioinput"
|
kind="audioinput"
|
||||||
onChange={this.handleInputChange} />
|
onChange={this.handleInputChange} />
|
||||||
<span className={styles.heading}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="app.audio.audioSettings.microphoneStreamLabel"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<AudioStreamVolume
|
|
||||||
className={styles.item}
|
|
||||||
deviceId={this.state.inputDeviceId} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.containerRightHalfContent}>
|
</div>
|
||||||
<span className={styles.heading}>
|
<div className={styles.col}>
|
||||||
<FormattedMessage
|
<div className={styles.formElement}>
|
||||||
id="app.audio.audioSettings.speakerSourceLabel"
|
<label className={cx(styles.label, styles.labelSmall)}>
|
||||||
/>
|
Speaker source
|
||||||
</span>
|
</label>
|
||||||
<DeviceSelector
|
<DeviceSelector
|
||||||
className={styles.item}
|
value={this.state.outputDeviceId}
|
||||||
|
className={styles.select}
|
||||||
kind="audiooutput"
|
kind="audiooutput"
|
||||||
onChange={this.handleOutputChange} />
|
onChange={this.handleOutputChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.col}>
|
||||||
|
<div className={styles.formElement}>
|
||||||
|
<label className={cx(styles.label, styles.labelSmall)}>
|
||||||
|
Your audio stream volume
|
||||||
|
</label>
|
||||||
|
<AudioStreamVolume
|
||||||
|
deviceId={this.state.inputDeviceId}
|
||||||
|
className={styles.audioMeter} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.col}>
|
||||||
|
<label className={styles.label}> </label>
|
||||||
<AudioTestContainer/>
|
<AudioTestContainer/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.enterAudio}>
|
||||||
<EnterAudioContainer isFullAudio={true}/>
|
<EnterAudioContainer isFullAudio={true}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,23 +43,23 @@ class JoinAudio extends React.Component {
|
|||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.center}>
|
<div className={styles.closeBtn}>
|
||||||
<Button className={styles.closeBtn}
|
<Button className={styles.closeBtn}
|
||||||
label={intl.formatMessage(intlMessages.closeLabel)}
|
label={intl.formatMessage(intlMessages.closeLabel)}
|
||||||
icon={'close'}
|
icon={'close'}
|
||||||
size={'lg'}
|
size={'lg'}
|
||||||
circle={true}
|
|
||||||
hideLabel={true}
|
hideLabel={true}
|
||||||
onClick={this.handleClose}
|
onClick={this.handleClose}
|
||||||
/>
|
/>
|
||||||
<div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.title}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="app.audioModal.audioChoiceLabel"
|
id="app.audioModal.audioChoiceLabel"
|
||||||
description="app.audioModal.audioChoiceDescription"
|
description="app.audioModal.audioChoiceDescription"
|
||||||
defaultMessage="How would you like to join the audio?"
|
defaultMessage="How would you like to join the audio?"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<Button className={styles.audioBtn}
|
<Button className={styles.audioBtn}
|
||||||
label={intl.formatMessage(intlMessages.microphoneLabel)}
|
label={intl.formatMessage(intlMessages.microphoneLabel)}
|
||||||
@ -68,6 +68,9 @@ class JoinAudio extends React.Component {
|
|||||||
size={'jumbo'}
|
size={'jumbo'}
|
||||||
onClick={this.openAudio}
|
onClick={this.openAudio}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<span className={styles.verticalLine}>
|
||||||
|
</span>
|
||||||
<Button className={styles.audioBtn}
|
<Button className={styles.audioBtn}
|
||||||
label={intl.formatMessage(intlMessages.listenOnlyLabel)}
|
label={intl.formatMessage(intlMessages.listenOnlyLabel)}
|
||||||
icon={'listen'}
|
icon={'listen'}
|
||||||
|
@ -1,159 +1,164 @@
|
|||||||
@import "../../stylesheets/variables/_all";
|
@import "../../stylesheets/variables/_all";
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
text-align: center;
|
display: flex;
|
||||||
font-size: $font-size-large;
|
justify-content: center;
|
||||||
padding-top: 40px;
|
align-items: center;
|
||||||
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeBtn {
|
.closeBtn {
|
||||||
position: absolute;
|
background-color: #FFFFFF;
|
||||||
right: 10px;
|
border: none;
|
||||||
top: 10px;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
i {
|
||||||
|
color: $color-gray-light;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the close button style
|
Button.audioBtn {
|
||||||
Button.closeBtn span:first-child {
|
i{
|
||||||
color: $color-gray-light;
|
color: #3c5764;
|
||||||
background: none;
|
}
|
||||||
background-color: $color-primary;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the audio button icon colour
|
// Modifies the audio button icon colour
|
||||||
Button.audioBtn span:first-child {
|
Button.audioBtn span:first-child {
|
||||||
color: #25385D;
|
color: #1b3c4b;
|
||||||
border: 5px solid $color-white;
|
background-color: #f1f8ff;
|
||||||
background-color: $color-primary;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
border: 5px solid #f1f8ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When hovering over a button of class audioBtn, change the border colour of first span-child
|
// When hovering over a button of class audioBtn, change the border colour of first span-child
|
||||||
Button.audioBtn:hover span:first-child {
|
Button.audioBtn:hover span:first-child {
|
||||||
border: 5px solid $color-primary;
|
border: 5px solid $color-primary;
|
||||||
|
background-color: #f1f8ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the button label text
|
// Modifies the button label text
|
||||||
Button.audioBtn span:last-child {
|
Button.audioBtn span:last-child {
|
||||||
color: $color-gray-dark;
|
color: black;
|
||||||
font-size: 30%;
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button.audioBtn:first-of-type {
|
Button.audioBtn:first-of-type {
|
||||||
margin-right: 70px;
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.audioBtn:last-of-type {
|
||||||
|
margin-left: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
padding: 10px;
|
padding: 1em;
|
||||||
min-height: 350px;
|
min-height: 20rem;
|
||||||
min-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio settings menu
|
|
||||||
.half {
|
|
||||||
width: 50%;
|
|
||||||
float: left;
|
|
||||||
padding-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.half label {
|
|
||||||
font-size: 0.75em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $color-primary;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.backBtn {
|
.backBtn {
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 10px;
|
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
i {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.playSound {
|
.topRow {
|
||||||
border: none;
|
align-items: center;
|
||||||
box-shadow: none;
|
display: flex;
|
||||||
}
|
|
||||||
|
|
||||||
.enterBtn {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
right: 10px;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.containerLeftHalf {
|
|
||||||
width: 50%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.containerRightHalf {
|
|
||||||
width: 50%;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.containerFull{
|
|
||||||
width: 100%;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.containerLeftHalfContent {
|
|
||||||
@extend .containerLeftHalf;
|
|
||||||
@extend .row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.containerRightHalfContent {
|
|
||||||
@extend .containerRightHalf;
|
|
||||||
@extend .row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: $md-padding-y;
|
|
||||||
width: 85%;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
border-bottom: 3px solid;
|
|
||||||
border-top:0px;
|
|
||||||
border-left: 0px;
|
|
||||||
border-right: 0px;
|
|
||||||
outline:0px;
|
|
||||||
border-color: $color-gray-dark;
|
|
||||||
text-decoration-color: $color-gray-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: $color-gray-dark;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: $font-size-large;
|
|
||||||
margin-top: -1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioNote {
|
.audioNote {
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 1.75em;
|
font-size: 0.9rem;
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.title {
|
||||||
font-weight: 700;
|
text-align: center;
|
||||||
font-size: $font-size-small;
|
margin: auto;
|
||||||
display: inline-block;
|
color: black;
|
||||||
margin-bottom: .5em;
|
font-weight: 400;
|
||||||
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.joinButton {
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 0;
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
padding-right: 0.1rem;
|
||||||
|
padding-left: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelSmall {
|
||||||
|
color: black;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formElement {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 11em;
|
display: flex;
|
||||||
top: 3em;
|
flex-flow: column;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 0.1rem solid $color-gray-lighter;
|
||||||
|
color: $color-gray-light;
|
||||||
|
width: 100%;
|
||||||
|
// appearance: none;
|
||||||
|
height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audioMeter {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pullContentRight {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-flow: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalLine {
|
||||||
|
color: #f3f6f9;
|
||||||
|
border-left: 1px solid;
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enterAudio {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chooseAudio {
|
||||||
|
position:absolute;
|
||||||
|
left:50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -28,11 +28,24 @@ export default class Chat extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.chat}>
|
<section className={styles.chat}>
|
||||||
|
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Link className={styles.closeChat} to="/users">
|
<div className={styles.title}>
|
||||||
|
<Link to="/users">
|
||||||
<Icon iconName="left_arrow"/> {title}
|
<Icon iconName="left_arrow"/> {title}
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles.closeIcon}>
|
||||||
|
{
|
||||||
|
((this.props.chatID == 'public') ?
|
||||||
|
null :
|
||||||
|
<Link to="/users">
|
||||||
|
<Icon iconName="close" onClick={() => actions.handleClosePrivateChat(chatID)}/>
|
||||||
|
</Link>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<MessageList
|
<MessageList
|
||||||
chatId={chatID}
|
chatId={chatID}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
|
@ -95,6 +95,9 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
|||||||
isChatLocked,
|
isChatLocked,
|
||||||
scrollPosition,
|
scrollPosition,
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
|
handleClosePrivateChat: chatID => ChatService.closePrivateChat(chatID),
|
||||||
|
|
||||||
handleSendMessage: message => {
|
handleSendMessage: message => {
|
||||||
let sentMessage = ChatService.sendMessage(chatID, message);
|
let sentMessage = ChatService.sendMessage(chatID, message);
|
||||||
ChatService.updateScrollPosition(chatID, null); //null so its scrolls to bottom
|
ChatService.updateScrollPosition(chatID, null); //null so its scrolls to bottom
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
import Button from '/imports/ui/components/button/component';
|
import Button from '/imports/ui/components/button/component';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
import UserListService from '/imports/ui/components/user-list/service';
|
||||||
|
import SettingsService from '/imports/ui/components/settings/service';
|
||||||
|
|
||||||
|
class ChatNotificationContainer extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.audio = new Audio('/html5client/resources/sounds/notify.mp3');
|
||||||
|
}
|
||||||
|
|
||||||
|
playAudio() {
|
||||||
|
if (this.props.disableAudio) return;
|
||||||
|
return _.debounce(() => this.audio.play(), this.audio.duration * 1000)();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.unreadMessagesCount > prevProps.unreadMessagesCount) {
|
||||||
|
this.playAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createContainer(({ currentChatID }) => {
|
||||||
|
const AppSettings = SettingsService.getSettingsFor('application');
|
||||||
|
|
||||||
|
const unreadMessagesCount = UserListService.getOpenChats()
|
||||||
|
.map(chat => chat.unreadCounter)
|
||||||
|
.filter(userID => userID !== Auth.userID)
|
||||||
|
.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
disableAudio: !AppSettings.chatAudioNotifications,
|
||||||
|
unreadMessagesCount,
|
||||||
|
};
|
||||||
|
}, ChatNotificationContainer);
|
@ -4,8 +4,10 @@ import Meetings from '/imports/api/meetings';
|
|||||||
|
|
||||||
import Auth from '/imports/ui/services/auth';
|
import Auth from '/imports/ui/services/auth';
|
||||||
import UnreadMessages from '/imports/ui/services/unread-messages';
|
import UnreadMessages from '/imports/ui/services/unread-messages';
|
||||||
|
import Storage from '/imports/ui/services/storage/session';
|
||||||
|
|
||||||
import { callServer } from '/imports/ui/services/api';
|
import { callServer } from '/imports/ui/services/api';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||||
const GROUPING_MESSAGES_WINDOW = CHAT_CONFIG.grouping_messages_window;
|
const GROUPING_MESSAGES_WINDOW = CHAT_CONFIG.grouping_messages_window;
|
||||||
@ -20,6 +22,9 @@ const PUBLIC_CHAT_USERNAME = CHAT_CONFIG.public_username;
|
|||||||
|
|
||||||
const ScrollCollection = new Mongo.Collection(null);
|
const ScrollCollection = new Mongo.Collection(null);
|
||||||
|
|
||||||
|
// session for closed chat list
|
||||||
|
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
|
||||||
|
|
||||||
/* TODO: Same map is done in the user-list/service we should share this someway */
|
/* TODO: Same map is done in the user-list/service we should share this someway */
|
||||||
|
|
||||||
const mapUser = (user) => ({
|
const mapUser = (user) => ({
|
||||||
@ -193,6 +198,13 @@ const sendMessage = (receiverID, message) => {
|
|||||||
from_color: 0,
|
from_color: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY);
|
||||||
|
|
||||||
|
// Remove the chat that user send messages from the session.
|
||||||
|
if (_.indexOf(currentClosedChats, receiver.id) > -1) {
|
||||||
|
Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, receiver.id));
|
||||||
|
}
|
||||||
|
|
||||||
callServer('sendChat', messagePayload);
|
callServer('sendChat', messagePayload);
|
||||||
|
|
||||||
return messagePayload;
|
return messagePayload;
|
||||||
@ -215,6 +227,17 @@ const updateUnreadMessage = (receiverID, timestamp) => {
|
|||||||
return UnreadMessages.update(receiverID, timestamp);
|
return UnreadMessages.update(receiverID, timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closePrivateChat = (chatID) => {
|
||||||
|
|
||||||
|
let currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || [];
|
||||||
|
|
||||||
|
if (_.indexOf(currentClosedChats, chatID) < 0) {
|
||||||
|
currentClosedChats.push(chatID);
|
||||||
|
|
||||||
|
Storage.setItem(CLOSED_CHAT_LIST_KEY, currentClosedChats);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getPublicMessages,
|
getPublicMessages,
|
||||||
getPrivateMessages,
|
getPrivateMessages,
|
||||||
@ -226,4 +249,5 @@ export default {
|
|||||||
updateScrollPosition,
|
updateScrollPosition,
|
||||||
updateUnreadMessage,
|
updateUnreadMessage,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
closePrivateChat,
|
||||||
};
|
};
|
||||||
|
@ -6,23 +6,38 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeChat {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin-top: $lg-padding-x - $sm-padding-x;
|
margin-top: $lg-padding-x - $sm-padding-x;
|
||||||
margin-bottom: $lg-padding-x;
|
margin-bottom: $lg-padding-x;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@extend %text-elipsis;
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
> [class^="icon-bbb-"],
|
> [class^="icon-bbb-"],
|
||||||
> [class*=" icon-bbb-"] {
|
> [class*=" icon-bbb-"] {
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[class='icon-bbb-left_arrow'] {
|
.closeIcon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class='icon-bbb-left_arrow'],
|
||||||
|
[class='icon-bbb-close']{
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,19 @@ import { findDOMNode } from 'react-dom';
|
|||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import DropdownTrigger from './trigger/component';
|
import DropdownTrigger from './trigger/component';
|
||||||
import DropdownContent from './content/component';
|
import DropdownContent from './content/component';
|
||||||
|
import Button from '/imports/ui/components/button/component';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const FOCUSABLE_CHILDREN = `[tabindex]:not([tabindex="-1"]), a, input, button`;
|
const FOCUSABLE_CHILDREN = `[tabindex]:not([tabindex="-1"]), a, input, button`;
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
close: {
|
||||||
|
id: 'app.dropdown.close',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
/**
|
/**
|
||||||
* The dropdown needs a trigger and a content component as childrens
|
* The dropdown needs a trigger and a content component as childrens
|
||||||
@ -44,7 +53,7 @@ const defaultProps = {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Dropdown extends Component {
|
class Dropdown extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { isOpen: false, };
|
this.state = { isOpen: false, };
|
||||||
@ -113,7 +122,7 @@ export default class Dropdown extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, className, style } = this.props;
|
const { children, className, style, intl } = this.props;
|
||||||
|
|
||||||
let trigger = children.find(x => x.type === DropdownTrigger);
|
let trigger = children.find(x => x.type === DropdownTrigger);
|
||||||
let content = children.find(x => x.type === DropdownContent);
|
let content = children.find(x => x.type === DropdownContent);
|
||||||
@ -137,6 +146,14 @@ export default class Dropdown extends Component {
|
|||||||
<div style={style} className={cx(styles.dropdown, className)}>
|
<div style={style} className={cx(styles.dropdown, className)}>
|
||||||
{trigger}
|
{trigger}
|
||||||
{content}
|
{content}
|
||||||
|
{ this.state.isOpen ?
|
||||||
|
<Button
|
||||||
|
className={styles.close}
|
||||||
|
label={intl.formatMessage(intlMessages.close)}
|
||||||
|
size={'lg'}
|
||||||
|
color={'default'}
|
||||||
|
onClick={this.handleHide}
|
||||||
|
/> : null }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,3 +161,4 @@ export default class Dropdown extends Component {
|
|||||||
|
|
||||||
Dropdown.propTypes = propTypes;
|
Dropdown.propTypes = propTypes;
|
||||||
Dropdown.defaultProps = defaultProps;
|
Dropdown.defaultProps = defaultProps;
|
||||||
|
export default injectIntl(Dropdown);
|
||||||
|
@ -40,8 +40,10 @@ export default class DropdownContent extends Component {
|
|||||||
style={style}
|
style={style}
|
||||||
aria-expanded={this.props['aria-expanded']}
|
aria-expanded={this.props['aria-expanded']}
|
||||||
className={cx(styles.content, styles[placementName], className)}>
|
className={cx(styles.content, styles[placementName], className)}>
|
||||||
|
<div className={styles.scrollable}>
|
||||||
{boundChildren}
|
{boundChildren}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import Icon from '/imports/ui/components/icon/component';
|
import Icon from '/imports/ui/components/icon/component';
|
||||||
|
@ -10,6 +10,11 @@
|
|||||||
padding: ($line-height-computed / 2);
|
padding: ($line-height-computed / 2);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
@include mq($small-only) {
|
||||||
|
font-size: $font-size-large * 1.1;
|
||||||
|
padding: $line-height-computed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -32,6 +37,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
@include mq($small-only) {
|
||||||
|
padding: ($line-height-computed / 1.5) 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import "../../stylesheets/variables/_all";
|
@import "../../stylesheets/variables/_all";
|
||||||
|
@import "../../stylesheets/mixins/_scrollable";
|
||||||
|
|
||||||
$dropdown-bg: $color-white;
|
$dropdown-bg: $color-white;
|
||||||
$dropdown-color: $color-text;
|
$dropdown-color: $color-text;
|
||||||
@ -18,7 +19,6 @@ $dropdown-caret-height: 8px;
|
|||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||||
border: 1px solid rgba(0, 0, 0, .15);
|
border: 1px solid rgba(0, 0, 0, .15);
|
||||||
padding: $line-height-computed / 2;
|
padding: $line-height-computed / 2;
|
||||||
// min-width: 150px;
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|
||||||
&:after, &:before {
|
&:after, &:before {
|
||||||
@ -35,10 +35,55 @@ $dropdown-caret-height: 8px;
|
|||||||
&[aria-expanded="true"] {
|
&[aria-expanded="true"] {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include mq($small-only) {
|
||||||
|
z-index: 1015;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 0 $line-height-computed * 5.25 0 !important;
|
||||||
|
transform: translateX(0) translateY(0) !important;
|
||||||
|
|
||||||
|
&:after, &:before {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
@include mq($small-only) {
|
||||||
|
@include scrollbox-vertical();
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger {}
|
.trigger {}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 1rem;
|
||||||
|
z-index: 1011;
|
||||||
|
font-size: $font-size-large * 1.1;
|
||||||
|
width: calc(100% - #{($line-height-computed * 2)});
|
||||||
|
left: $line-height-computed;
|
||||||
|
box-shadow: 0 0 0 2rem #fff;
|
||||||
|
|
||||||
|
@include mq($small-only) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Placements
|
/* Placements
|
||||||
* ==========
|
* ==========
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import styles from './styles.scss';
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
500: {
|
||||||
|
id: 'app.error.500',
|
||||||
|
defaultMessage: 'Ops, something went wrong',
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
id: 'app.error.404',
|
||||||
|
defaultMessage: 'Not Found',
|
||||||
|
},
|
||||||
|
401: {
|
||||||
|
id: 'app.about.401',
|
||||||
|
defaultMessage: 'Unauthorized',
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
id: 'app.about.403',
|
||||||
|
defaultMessage: 'Forbidden',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
code: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
code: 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ErrorScreen extends Component {
|
||||||
|
render() {
|
||||||
|
const { intl, code, children } = this.props;
|
||||||
|
|
||||||
|
let formatedMessage = intl.formatMessage(intlMessages[500]);
|
||||||
|
|
||||||
|
if (code in intlMessages) {
|
||||||
|
formatedMessage = intl.formatMessage(intlMessages[code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.background}>
|
||||||
|
<h1 className={styles.code}>
|
||||||
|
{code}
|
||||||
|
</h1>
|
||||||
|
<h1 className={styles.message}>
|
||||||
|
{formatedMessage}
|
||||||
|
</h1>
|
||||||
|
<div className={styles.content}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectIntl(ErrorScreen);
|
||||||
|
|
||||||
|
ErrorScreen.propTypes = propTypes;
|
||||||
|
ErrorScreen.defaultProps = defaultProps;
|
@ -1,18 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import styles from './styles.scss';
|
|
||||||
|
|
||||||
import Icon from '../icon/component';
|
|
||||||
|
|
||||||
class KickedScreen extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.background}>
|
|
||||||
<Icon iconName="sad" className={styles.icon}/>
|
|
||||||
<div className={styles.message}>
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default KickedScreen;
|
|
@ -1,9 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
import Modal from '/imports/ui/components/modal/component';
|
import Modal from '/imports/ui/components/modal/component';
|
||||||
import LocalStorage from '/imports/ui/services/storage/local.js';
|
|
||||||
import { clearModal } from '/imports/ui/components/app/service';
|
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
const intlMessages = defineMessages({
|
||||||
title: {
|
title: {
|
||||||
@ -33,33 +31,19 @@ const intlMessages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class LeaveConfirmation extends Component {
|
class LeaveConfirmation extends Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.handleLeaveConfirmation = this.handleLeaveConfirmation.bind(this);
|
|
||||||
this.handleCancleLogout = this.handleCancleLogout.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLeaveConfirmation() {
|
|
||||||
Auth.completeLogout();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancleLogout() {
|
|
||||||
clearModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl } = this.props;
|
const { intl, router } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={intl.formatMessage(intlMessages.title)}
|
title={intl.formatMessage(intlMessages.title)}
|
||||||
confirm={{
|
confirm={{
|
||||||
callback: this.handleLeaveConfirmation,
|
callback: () => router.push('/logout'),
|
||||||
label: intl.formatMessage(intlMessages.confirmLabel),
|
label: intl.formatMessage(intlMessages.confirmLabel),
|
||||||
description: intl.formatMessage(intlMessages.confirmDesc),
|
description: intl.formatMessage(intlMessages.confirmDesc),
|
||||||
}}
|
}}
|
||||||
dismiss={{
|
dismiss={{
|
||||||
callback: this.handleCancleLogout,
|
callback: () => null,
|
||||||
label: intl.formatMessage(intlMessages.dismissLabel),
|
label: intl.formatMessage(intlMessages.dismissLabel),
|
||||||
description: intl.formatMessage(intlMessages.dismissDesc),
|
description: intl.formatMessage(intlMessages.dismissDesc),
|
||||||
}}>
|
}}>
|
||||||
@ -69,4 +53,4 @@ class LeaveConfirmation extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(LeaveConfirmation);
|
export default withRouter(injectIntl(LeaveConfirmation));
|
||||||
|
@ -46,7 +46,10 @@ export default class Modal extends Component {
|
|||||||
|
|
||||||
handleDismiss() {
|
handleDismiss() {
|
||||||
const { dismiss } = this.props;
|
const { dismiss } = this.props;
|
||||||
|
if (dismiss && dismiss.callback) {
|
||||||
dismiss.callback(...arguments);
|
dismiss.callback(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
clearModal();
|
clearModal();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ import { showModal } from '/imports/ui/components/app/service';
|
|||||||
|
|
||||||
import Button from '../button/component';
|
import Button from '../button/component';
|
||||||
import RecordingIndicator from './recording-indicator/component';
|
import RecordingIndicator from './recording-indicator/component';
|
||||||
import SettingsDropdown from './settings-dropdown/component';
|
import SettingsDropdownContainer from './settings-dropdown/container';
|
||||||
import Icon from '/imports/ui/components/icon/component';
|
import Icon from '/imports/ui/components/icon/component';
|
||||||
import BreakoutJoinConfirmation from '/imports/ui/components/breakout-join-confirmation/component';
|
import BreakoutJoinConfirmation from '/imports/ui/components/breakout-join-confirmation/component';
|
||||||
import Dropdown from '/imports/ui/components/dropdown/component';
|
import Dropdown from '/imports/ui/components/dropdown/component';
|
||||||
@ -85,7 +85,7 @@ class NavBar extends Component {
|
|||||||
<RecordingIndicator beingRecorded={beingRecorded}/>
|
<RecordingIndicator beingRecorded={beingRecorded}/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
<SettingsDropdown />
|
<SettingsDropdownContainer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -57,7 +57,7 @@ export default withRouter(createContainer(({ location, router }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const breakouts = Service.getBreakouts();
|
const breakouts = Service.getBreakouts();
|
||||||
const currentUserId = Auth.getCredentials().requesterUserId;
|
const currentUserId = Auth.userID;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
breakouts,
|
breakouts,
|
||||||
|
@ -4,7 +4,7 @@ import Breakouts from '/imports/api/breakouts';
|
|||||||
const getBreakouts = () => Breakouts.find().fetch();
|
const getBreakouts = () => Breakouts.find().fetch();
|
||||||
|
|
||||||
const getBreakoutJoinURL = (breakout) => {
|
const getBreakoutJoinURL = (breakout) => {
|
||||||
const currentUserId = Auth.getCredentials().requesterUserId;
|
const currentUserId = Auth.userID;
|
||||||
|
|
||||||
if (breakout.users) {
|
if (breakout.users) {
|
||||||
const user = breakout.users.find(user => user.userId === currentUserId);
|
const user = breakout.users.find(user => user.userId === currentUserId);
|
||||||
|
@ -53,44 +53,16 @@ const intlMessages = defineMessages({
|
|||||||
id: 'app.navBar.settingsDropdown.leaveSessionDesc',
|
id: 'app.navBar.settingsDropdown.leaveSessionDesc',
|
||||||
defaultMessage: 'Leave the meeting',
|
defaultMessage: 'Leave the meeting',
|
||||||
},
|
},
|
||||||
|
exitFullScreenDesc: {
|
||||||
|
id: 'app.navBar.settingsDropdown.exitFullScreenDesc',
|
||||||
|
defaultMessage: 'exit fullscreen mode',
|
||||||
|
},
|
||||||
|
exitFullScreenLabel: {
|
||||||
|
id: 'app.navBar.settingsDropdown.exitFullScreenLabel',
|
||||||
|
defaultMessage: 'Exit fullscreen',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleFullScreen = () => {
|
|
||||||
let element = document.documentElement;
|
|
||||||
|
|
||||||
if (document.fullscreenEnabled
|
|
||||||
|| document.mozFullScreenEnabled
|
|
||||||
|| document.webkitFullscreenEnabled) {
|
|
||||||
|
|
||||||
// If the page is already fullscreen, exit fullscreen
|
|
||||||
if (document.fullscreenElement
|
|
||||||
|| document.webkitFullscreenElement
|
|
||||||
|| document.mozFullScreenElement
|
|
||||||
|| document.msFullscreenElement) {
|
|
||||||
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else if (document.mozCancelFullScreen) {
|
|
||||||
document.mozCancelFullScreen();
|
|
||||||
} else if (document.webkitExitFullscreen) {
|
|
||||||
document.webkitExitFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the page is not currently fullscreen, make fullscreen
|
|
||||||
} else {
|
|
||||||
if (element.requestFullscreen) {
|
|
||||||
element.requestFullscreen();
|
|
||||||
} else if (element.mozRequestFullScreen) {
|
|
||||||
element.mozRequestFullScreen();
|
|
||||||
} else if (element.webkitRequestFullscreen) {
|
|
||||||
element.webkitRequestFullscreen();
|
|
||||||
} else if (element.msRequestFullscreen) {
|
|
||||||
element.msRequestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openSettings = () => showModal(<SettingsMenuContainer />);
|
const openSettings = () => showModal(<SettingsMenuContainer />);
|
||||||
|
|
||||||
const openAbout = () => showModal(<AboutContainer />);
|
const openAbout = () => showModal(<AboutContainer />);
|
||||||
@ -103,7 +75,17 @@ class SettingsDropdown extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl } = this.props;
|
|
||||||
|
const { intl, isFullScreen } = this.props;
|
||||||
|
|
||||||
|
let fullScreenLabel = intl.formatMessage(intlMessages.fullscreenLabel);
|
||||||
|
let fullScreenDesc = intl.formatMessage(intlMessages.fullscreenDesc);
|
||||||
|
|
||||||
|
if (isFullScreen) {
|
||||||
|
fullScreenLabel = intl.formatMessage(intlMessages.exitFullScreenLabel);
|
||||||
|
fullScreenDesc = intl.formatMessage(intlMessages.exitFullScreenDesc);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown ref="dropdown">
|
<Dropdown ref="dropdown">
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
@ -124,9 +106,9 @@ class SettingsDropdown extends Component {
|
|||||||
<DropdownList>
|
<DropdownList>
|
||||||
<DropdownListItem
|
<DropdownListItem
|
||||||
icon="fullscreen"
|
icon="fullscreen"
|
||||||
label={intl.formatMessage(intlMessages.fullscreenLabel)}
|
label={fullScreenLabel}
|
||||||
description={intl.formatMessage(intlMessages.fullscreenDesc)}
|
description={fullScreenDesc}
|
||||||
onClick={toggleFullScreen.bind(this)}
|
onClick={this.props.handleToggleFullscreen}
|
||||||
/>
|
/>
|
||||||
<DropdownListItem
|
<DropdownListItem
|
||||||
icon="more"
|
icon="more"
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import SettingsDropdown from './component';
|
||||||
|
import Service from './service';
|
||||||
|
|
||||||
|
export default class SettingsDropdownContainer extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isFullScreen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleFullscreenChange = this.handleFullscreenChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const fullscreenChangedEvents = ['fullscreenchange',
|
||||||
|
'webkitfullscreenchange',
|
||||||
|
'mozfullscreenchange',
|
||||||
|
'MSFullscreenChange', ];
|
||||||
|
|
||||||
|
fullscreenChangedEvents.forEach(event =>
|
||||||
|
document.addEventListener(event, this.handleFullscreenChange));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const fullscreenChangedEvents = ['fullscreenchange',
|
||||||
|
'webkitfullscreenchange',
|
||||||
|
'mozfullscreenchange',
|
||||||
|
'MSFullscreenChange', ];
|
||||||
|
|
||||||
|
fullscreenChangedEvents.forEach(event =>
|
||||||
|
document.removeEventListener(event, this.fullScreenToggleCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFullscreenChange() {
|
||||||
|
if (screen.height - 1 <= window.innerHeight) {
|
||||||
|
// browser is probably in fullscreen
|
||||||
|
this.setState({ isFullScreen: true });
|
||||||
|
}else {
|
||||||
|
this.setState({ isFullScreen: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const handleToggleFullscreen = Service.toggleFullScreen;
|
||||||
|
const isFullScreen = this.state.isFullScreen;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsDropdown
|
||||||
|
handleToggleFullscreen={handleToggleFullscreen}
|
||||||
|
isFullScreen={isFullScreen}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
toggleFullScreen = () => {
|
||||||
|
let element = document.documentElement;
|
||||||
|
|
||||||
|
if (document.fullscreenEnabled
|
||||||
|
|| document.mozFullScreenEnabled
|
||||||
|
|| document.webkitFullscreenEnabled) {
|
||||||
|
|
||||||
|
// If the page is already fullscreen, exit fullscreen
|
||||||
|
if (document.fullscreenElement
|
||||||
|
|| document.webkitFullscreenElement
|
||||||
|
|| document.mozFullScreenElement
|
||||||
|
|| document.msFullscreenElement) {
|
||||||
|
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen();
|
||||||
|
} else if (document.webkitExitFullscreen) {
|
||||||
|
document.webkitExitFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the page is not currently fullscreen, make fullscreen
|
||||||
|
} else {
|
||||||
|
if (element.requestFullscreen) {
|
||||||
|
element.requestFullscreen();
|
||||||
|
} else if (element.mozRequestFullScreen) {
|
||||||
|
element.mozRequestFullScreen();
|
||||||
|
} else if (element.webkitRequestFullscreen) {
|
||||||
|
element.webkitRequestFullscreen();
|
||||||
|
} else if (element.msRequestFullscreen) {
|
||||||
|
element.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
toggleFullScreen,
|
||||||
|
};
|
@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { createContainer } from 'meteor/react-meteor-data';
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import NavBarService from '../nav-bar/service';
|
import NavBarService from '../nav-bar/service';
|
||||||
import Auth from '/imports/ui/services/auth';
|
import Auth from '/imports/ui/services/auth';
|
||||||
import { humanizeSeconds } from '/imports/utils/humanizeSeconds';
|
import { humanizeSeconds } from '/imports/utils/humanizeSeconds';
|
||||||
|
@ -7,8 +7,8 @@ let getSlideData = (params) => {
|
|||||||
const { currentSlideNum, presentationId } = params;
|
const { currentSlideNum, presentationId } = params;
|
||||||
|
|
||||||
// Get userId and meetingId
|
// Get userId and meetingId
|
||||||
const userId = AuthSingleton.getCredentials().requesterUserId;
|
const userId = AuthSingleton.userID;
|
||||||
const meetingId = AuthSingleton.getCredentials().meetingId;
|
const meetingId = AuthSingleton.meetingID;
|
||||||
|
|
||||||
// Find the user object of this specific meeting and userid
|
// Find the user object of this specific meeting and userid
|
||||||
const currentUser = Users.findOne({
|
const currentUser = Users.findOne({
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import Modal from '/imports/ui/components/modal/component';
|
import Modal from '/imports/ui/components/modal/component';
|
||||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
|
|
||||||
@ -9,10 +8,8 @@ import Application from '/imports/ui/components/settings/submenus/application/co
|
|||||||
import Participants from '/imports/ui/components/settings/submenus/participants/component';
|
import Participants from '/imports/ui/components/settings/submenus/participants/component';
|
||||||
import Video from '/imports/ui/components/settings/submenus/video/component';
|
import Video from '/imports/ui/components/settings/submenus/video/component';
|
||||||
|
|
||||||
import Button from '../button/component';
|
|
||||||
import Icon from '../icon/component';
|
import Icon from '../icon/component';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import cx from 'classnames';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { createContainer } from 'meteor/react-meteor-data';
|
import { createContainer } from 'meteor/react-meteor-data';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import Settings from './component.jsx';
|
import Settings from './component';
|
||||||
import {
|
import {
|
||||||
getSettingsFor,
|
getSettingsFor,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
getClosedCaptionLocales,
|
getClosedCaptionLocales,
|
||||||
getUserRoles,
|
getUserRoles,
|
||||||
} from './service.js';
|
} from './service';
|
||||||
|
|
||||||
class SettingsContainer extends Component {
|
class SettingsContainer extends Component {
|
||||||
render() {
|
render() {
|
||||||
|
@ -2,7 +2,7 @@ import Storage from '/imports/ui/services/storage/session';
|
|||||||
import Users from '/imports/api/users';
|
import Users from '/imports/api/users';
|
||||||
import Captions from '/imports/api/captions';
|
import Captions from '/imports/api/captions';
|
||||||
import Auth from '/imports/ui/services/auth';
|
import Auth from '/imports/ui/services/auth';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const updateSettings = (obj) => {
|
const updateSettings = (obj) => {
|
||||||
Object.keys(obj).forEach(k => Storage.setItem(`settings_${k}`, obj[k]));
|
Object.keys(obj).forEach(k => Storage.setItem(`settings_${k}`, obj[k]));
|
||||||
|
@ -56,3 +56,13 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.testAudioBtn {
|
||||||
|
border: none;
|
||||||
|
padding-left: 0;
|
||||||
|
i {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
background-color: transparent;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import BaseMenu from '../base/component';
|
import BaseMenu from '../base/component';
|
||||||
import styles from '../styles.scss';
|
import styles from '../styles.scss';
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export default class AudioMenu extends BaseMenu {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.tabContent}>
|
<div>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h3 className={styles.title}>Audio</h3>
|
<h3 className={styles.title}>Audio</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,15 +2,20 @@ import Users from '/imports/api/users';
|
|||||||
import Chat from '/imports/api/chat';
|
import Chat from '/imports/api/chat';
|
||||||
import Auth from '/imports/ui/services/auth';
|
import Auth from '/imports/ui/services/auth';
|
||||||
import UnreadMessages from '/imports/ui/services/unread-messages';
|
import UnreadMessages from '/imports/ui/services/unread-messages';
|
||||||
|
import Storage from '/imports/ui/services/storage/session';
|
||||||
import { EMOJI_STATUSES } from '/imports/utils/statuses.js';
|
import { EMOJI_STATUSES } from '/imports/utils/statuses.js';
|
||||||
|
|
||||||
import { callServer } from '/imports/ui/services/api';
|
import { callServer } from '/imports/ui/services/api';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||||
const USER_CONFIG = Meteor.settings.public.user;
|
const USER_CONFIG = Meteor.settings.public.user;
|
||||||
const ROLE_MODERATOR = USER_CONFIG.role_moderator;
|
const ROLE_MODERATOR = USER_CONFIG.role_moderator;
|
||||||
const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private;
|
const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private;
|
||||||
|
|
||||||
|
// session for closed chat list
|
||||||
|
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
|
||||||
|
|
||||||
/* TODO: Same map is done in the chat/service we should share this someway */
|
/* TODO: Same map is done in the chat/service we should share this someway */
|
||||||
|
|
||||||
const mapUser = user => ({
|
const mapUser = user => ({
|
||||||
@ -170,7 +175,6 @@ const getUsers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getOpenChats = chatID => {
|
const getOpenChats = chatID => {
|
||||||
window.Users = Users;
|
|
||||||
|
|
||||||
let openChats = Chat
|
let openChats = Chat
|
||||||
.find({ 'message.chat_type': PRIVATE_CHAT_TYPE })
|
.find({ 'message.chat_type': PRIVATE_CHAT_TYPE })
|
||||||
@ -178,6 +182,7 @@ const getOpenChats = chatID => {
|
|||||||
.map(mapOpenChats);
|
.map(mapOpenChats);
|
||||||
|
|
||||||
let currentUserId = Auth.userID;
|
let currentUserId = Auth.userID;
|
||||||
|
|
||||||
if (chatID) {
|
if (chatID) {
|
||||||
openChats.push(chatID);
|
openChats.push(chatID);
|
||||||
}
|
}
|
||||||
@ -193,6 +198,28 @@ const getOpenChats = chatID => {
|
|||||||
return op;
|
return op;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || [];
|
||||||
|
let filteredChatList = [];
|
||||||
|
|
||||||
|
openChats.forEach((op) => {
|
||||||
|
|
||||||
|
// When a new private chat message is received, ensure the conversation view is restored.
|
||||||
|
if (op.unreadCounter > 0) {
|
||||||
|
if (_.indexOf(currentClosedChats, op.id) > -1) {
|
||||||
|
Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, op.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare openChats with session and push it into filteredChatList
|
||||||
|
// if one of the openChat is not in session.
|
||||||
|
// It will pass to openChats.
|
||||||
|
if (_.indexOf(currentClosedChats, op.id) < 0) {
|
||||||
|
filteredChatList.push(op);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openChats = filteredChatList;
|
||||||
|
|
||||||
openChats.push({
|
openChats.push({
|
||||||
id: 'public',
|
id: 'public',
|
||||||
name: 'Public Chat',
|
name: 'Public Chat',
|
||||||
|
@ -7,7 +7,7 @@ import { withRouter } from 'react-router';
|
|||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import Dropdown from '/imports/ui/components/dropdown/component';
|
import Dropdown from '/imports/ui/components/dropdown/component';
|
||||||
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
|
||||||
|
@ -8,7 +8,7 @@ function callServer(name) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = Auth.getCredentials();
|
const credentials = Auth.credentials;
|
||||||
|
|
||||||
// slice off the first element. That is the function name but we already have that.
|
// slice off the first element. That is the function name but we already have that.
|
||||||
const args = Array.prototype.slice.call(arguments, 1);
|
const args = Array.prototype.slice.call(arguments, 1);
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
import { Tracker } from 'meteor/tracker';
|
||||||
|
|
||||||
import Storage from '/imports/ui/services/storage/session';
|
import Storage from '/imports/ui/services/storage/session';
|
||||||
|
|
||||||
|
import Users from '/imports/api/users';
|
||||||
import { callServer } from '/imports/ui/services/api';
|
import { callServer } from '/imports/ui/services/api';
|
||||||
|
|
||||||
class Auth {
|
class Auth {
|
||||||
@ -6,11 +10,10 @@ class Auth {
|
|||||||
this._meetingID = Storage.getItem('meetingID');
|
this._meetingID = Storage.getItem('meetingID');
|
||||||
this._userID = Storage.getItem('userID');
|
this._userID = Storage.getItem('userID');
|
||||||
this._authToken = Storage.getItem('authToken');
|
this._authToken = Storage.getItem('authToken');
|
||||||
this._logoutURL = Storage.getItem('logoutURL');
|
this._loggedIn = {
|
||||||
|
value: false,
|
||||||
if (!this._logoutURL) {
|
tracker: new Tracker.Dependency,
|
||||||
this._setLogOut();
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get meetingID() {
|
get meetingID() {
|
||||||
@ -40,22 +43,17 @@ class Auth {
|
|||||||
Storage.setItem('authToken', this._authToken);
|
Storage.setItem('authToken', this._authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoutURL() {
|
get loggedIn() {
|
||||||
return this._logoutURL;
|
this._loggedIn.tracker.depend();
|
||||||
|
return this._loggedIn.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
set logoutURL(logoutURL) {
|
set loggedIn(value) {
|
||||||
this._logoutURL = logoutURL;
|
this._loggedIn.value = value;
|
||||||
Storage.setItem('logoutURL', this._logoutURL);
|
this._loggedIn.tracker.changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
setCredentials(meeting, user, token) {
|
get credentials() {
|
||||||
this.meetingID = meeting;
|
|
||||||
this.userID = user;
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCredentials() {
|
|
||||||
return {
|
return {
|
||||||
meetingId: this.meetingID,
|
meetingId: this.meetingID,
|
||||||
requesterUserId: this.userID,
|
requesterUserId: this.userID,
|
||||||
@ -63,49 +61,114 @@ class Auth {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCredentials(callback) {
|
set credentials(value) {
|
||||||
|
throw 'Credentials are read-only';
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCredentials() {
|
||||||
this.meetingID = null;
|
this.meetingID = null;
|
||||||
this.userID = null;
|
this.userID = null;
|
||||||
this.token = null;
|
this.token = null;
|
||||||
|
this.loggedIn = false;
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
return Promise.resolve(...arguments);
|
||||||
return callback();
|
};
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (!this.loggedIn) {
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
completeLogout() {
|
return new Promise((resolve, reject) => {
|
||||||
let logoutURL = this.logoutURL;
|
callServer('userLogout', () => {
|
||||||
callServer('userLogout');
|
this.fetchLogoutUrl()
|
||||||
|
.then(this.clearCredentials)
|
||||||
this.clearCredentials(() => {
|
.then(resolve);
|
||||||
document.location.href = logoutURL;
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setLogOut() {
|
authenticate(meetingID, userID, token) {
|
||||||
let request;
|
if (arguments.length) {
|
||||||
let handleLogoutUrlError;
|
this.meetingID = meetingID;
|
||||||
|
this.userID = userID;
|
||||||
handleLogoutUrlError = function () {
|
this.token = token;
|
||||||
console.log('Error : could not find the logoutURL');
|
|
||||||
this.logoutURL = document.location.hostname;
|
|
||||||
};
|
|
||||||
|
|
||||||
// obtain the logoutURL
|
|
||||||
request = $.ajax({
|
|
||||||
dataType: 'json',
|
|
||||||
url: '/bigbluebutton/api/enter',
|
|
||||||
});
|
|
||||||
|
|
||||||
request.done(data => {
|
|
||||||
if (data.response.logoutURL != null) {
|
|
||||||
this.logoutURL = data.response.logoutURL;
|
|
||||||
} else {
|
|
||||||
return handleLogoutUrlError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this._subscribeToCurrentUser()
|
||||||
|
.then(this._addObserverToValidatedField.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscribeToCurrentUser() {
|
||||||
|
const credentials = this.credentials;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Tracker.autorun((c) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
c.stop();
|
||||||
|
reject('Authentication subscription timeout.');
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
const subscription = Meteor.subscribe('current-user', credentials);
|
||||||
|
if (!subscription.ready()) return;
|
||||||
|
|
||||||
|
resolve(c);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_addObserverToValidatedField(prevComp) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const validationTimeout = setTimeout(() => {
|
||||||
|
this.clearCredentials();
|
||||||
|
reject('Authentication timeout.');
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
const didValidate = () => {
|
||||||
|
this.loggedIn = true;
|
||||||
|
clearTimeout(validationTimeout);
|
||||||
|
prevComp.stop();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
Tracker.autorun((c) => {
|
||||||
|
const selector = { meetingId: this.meetingID, userId: this.userID };
|
||||||
|
const query = Users.find(selector);
|
||||||
|
|
||||||
|
if (query.count() && query.fetch()[0].validated) {
|
||||||
|
c.stop();
|
||||||
|
didValidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = query.observeChanges({
|
||||||
|
changed: (id, fields) => {
|
||||||
|
if (id !== this.userID) return;
|
||||||
|
|
||||||
|
if (fields.validated === true) {
|
||||||
|
c.stop();
|
||||||
|
didValidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.validated === false) {
|
||||||
|
c.stop();
|
||||||
|
this.clearCredentials();
|
||||||
|
reject('Authentication failed.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return request.fail(() => handleLogoutUrlError());
|
const credentials = this.credentials;
|
||||||
|
callServer('validateAuthToken', credentials);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchLogoutUrl() {
|
||||||
|
const url = `/bigbluebutton/api/enter`;
|
||||||
|
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => Promise.resolve(data.response.logoutURL));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import _ from 'underscore';
|
import _ from 'lodash';
|
||||||
import { Tracker } from 'meteor/tracker';
|
import { Tracker } from 'meteor/tracker';
|
||||||
import { EJSON } from 'meteor/ejson';
|
import { EJSON } from 'meteor/ejson';
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ $color-primary: #299AD5 !default;
|
|||||||
$color-success: #4DC0A2 !default;
|
$color-success: #4DC0A2 !default;
|
||||||
$color-danger: #EC6365 !default;
|
$color-danger: #EC6365 !default;
|
||||||
|
|
||||||
$color-background: #2A2D36 !default;
|
$color-background: $color-gray-dark !default;
|
||||||
|
|
||||||
$color-text: #8A95A5 !default;
|
$color-text: #8A95A5 !default;
|
||||||
$color-heading: #4E525E !default;
|
$color-heading: #4E525E !default;
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
"grunt-cli": "~1.2.0",
|
"grunt-cli": "~1.2.0",
|
||||||
"hiredis": "^0.5.0",
|
"hiredis": "^0.5.0",
|
||||||
"history": "~3.3.0",
|
"history": "~3.3.0",
|
||||||
"image-size": "~0.5.0",
|
|
||||||
"meteor-node-stubs": "^0.2.3",
|
"meteor-node-stubs": "^0.2.3",
|
||||||
"node-sass": "~3.8.0",
|
"node-sass": "~3.8.0",
|
||||||
"probe-image-size": "~2.1.1",
|
"probe-image-size": "~2.1.1",
|
||||||
@ -32,10 +31,9 @@
|
|||||||
"react-tabs": "^0.8.2",
|
"react-tabs": "^0.8.2",
|
||||||
"react-toggle": "^2.2.0",
|
"react-toggle": "^2.2.0",
|
||||||
"redis": "^2.6.2",
|
"redis": "^2.6.2",
|
||||||
"underscore": "~1.8.3",
|
|
||||||
"winston": "^2.3.1",
|
"winston": "^2.3.1",
|
||||||
"xml2js": "^0.4.17",
|
"xml2js": "^0.4.17",
|
||||||
"xmlhttprequest": "^1.8.0"
|
"lodash": "~4.17.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.3.6",
|
"autoprefixer": "^6.3.6",
|
||||||
|
@ -23,3 +23,5 @@ app:
|
|||||||
|
|
||||||
# Name displayed in the brower URL
|
# Name displayed in the brower URL
|
||||||
basename: '/html5client'
|
basename: '/html5client'
|
||||||
|
|
||||||
|
defaultLocale: 'en'
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"app.chat.titlePrivate": "Private Chat with {name}",
|
"app.chat.titlePrivate": "Private Chat with {name}",
|
||||||
"app.chat.partnerDisconnected": "{name} has left the meeting",
|
"app.chat.partnerDisconnected": "{name} has left the meeting",
|
||||||
"app.chat.moreMessages": "More messages below",
|
"app.chat.moreMessages": "More messages below",
|
||||||
"app.kickMessage": "You have been kicked out of the meeting",
|
|
||||||
"app.presentation.presentationToolbar.prevSlideLabel": "Previous slide",
|
"app.presentation.presentationToolbar.prevSlideLabel": "Previous slide",
|
||||||
"app.presentation.presentationToolbar.prevSlideDescrip": "Change the presentation to the previous slide",
|
"app.presentation.presentationToolbar.prevSlideDescrip": "Change the presentation to the previous slide",
|
||||||
"app.presentation.presentationToolbar.nextSlideLabel": "Next slide",
|
"app.presentation.presentationToolbar.nextSlideLabel": "Next slide",
|
||||||
@ -37,6 +36,8 @@
|
|||||||
"app.navBar.settingsDropdown.settingsDesc": "Change the general settings",
|
"app.navBar.settingsDropdown.settingsDesc": "Change the general settings",
|
||||||
"app.navBar.settingsDropdown.aboutDesc": "Show information about the client",
|
"app.navBar.settingsDropdown.aboutDesc": "Show information about the client",
|
||||||
"app.navBar.settingsDropdown.leaveSessionDesc": "Leave the meeting",
|
"app.navBar.settingsDropdown.leaveSessionDesc": "Leave the meeting",
|
||||||
|
"app.navBar.settingsDropdown.exitFullScreenLabel": "Exit fullscreen",
|
||||||
|
"app.navBar.settingsDropdown.exitFullScreenDesc": "Exit fullscreen mode",
|
||||||
"app.leaveConfirmation.title": "Leave Session",
|
"app.leaveConfirmation.title": "Leave Session",
|
||||||
"app.leaveConfirmation.message": "Do you want to leave this meeting?",
|
"app.leaveConfirmation.message": "Do you want to leave this meeting?",
|
||||||
"app.leaveConfirmation.confirmLabel": "Leave",
|
"app.leaveConfirmation.confirmLabel": "Leave",
|
||||||
@ -109,5 +110,11 @@
|
|||||||
"app.audio.audioSettings.speakerSourceLabel": "Speaker source",
|
"app.audio.audioSettings.speakerSourceLabel": "Speaker source",
|
||||||
"app.audio.audioSettings.microphoneStreamLabel": "Your audio stream volume",
|
"app.audio.audioSettings.microphoneStreamLabel": "Your audio stream volume",
|
||||||
"app.audio.listenOnly.backLabel": "Back",
|
"app.audio.listenOnly.backLabel": "Back",
|
||||||
"app.audio.listenOnly.closeLabel": "Close"
|
"app.audio.listenOnly.closeLabel": "Close",
|
||||||
|
"app.error.kicked": "You have been kicked out of the meeting",
|
||||||
|
"app.dropdown.close": "Close",
|
||||||
|
"app.error.500": "Ops, something went wrong",
|
||||||
|
"app.error.404": "Not found",
|
||||||
|
"app.error.401": "Unauthorized",
|
||||||
|
"app.error.403": "Forbidden"
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
-->
|
-->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||||
<metadata>
|
<metadata>
|
||||||
Created by FontForge 20161004 at Fri Dec 30 11:22:51 2016
|
Created by FontForge 20161004 at Thu Mar 23 10:01:15 2017
|
||||||
By Ghazi
|
By Ghazi
|
||||||
Copyright (c) 2016, BlindSide Networks Inc.
|
Copyright (c) 2016, BlindSide Networks Inc.
|
||||||
</metadata>
|
</metadata>
|
||||||
@ -19,7 +19,7 @@ Copyright (c) 2016, BlindSide Networks Inc.
|
|||||||
panose-1="2 0 5 3 0 0 0 0 0 0"
|
panose-1="2 0 5 3 0 0 0 0 0 0"
|
||||||
ascent="819"
|
ascent="819"
|
||||||
descent="-205"
|
descent="-205"
|
||||||
bbox="0 -205 1212 820"
|
bbox="0 -205 1024 820"
|
||||||
underline-thickness="51"
|
underline-thickness="51"
|
||||||
underline-position="-102"
|
underline-position="-102"
|
||||||
unicode-range="U+0020-E932"
|
unicode-range="U+0020-E932"
|
||||||
@ -31,20 +31,20 @@ Copyright (c) 2016, BlindSide Networks Inc.
|
|||||||
<glyph glyph-name="space" unicode=" " horiz-adv-x="524"
|
<glyph glyph-name="space" unicode=" " horiz-adv-x="524"
|
||||||
/>
|
/>
|
||||||
<glyph glyph-name="logout" unicode=""
|
<glyph glyph-name="logout" unicode=""
|
||||||
d="M160 -117h84c19 0 30 -14 30 -32c0 -22 -7 -34 -30 -34h-84c-83 0 -150 66 -150 150v670c0 83 67 150 150 150h84c16 0 30 -17 30 -32c0 -16 -9 -32 -30 -32h-84c-47 0 -87 -40 -87 -86v-667c0 -47 40 -87 87 -87zM676 -9c-6 -5 -14 -8 -22 -8c-7 0 -15 3 -21 8
|
d="M160 -117h84c19 0 30 -14 30 -32c0 -16 -7 -34 -30 -34h-84c-83 0 -150 66 -150 150v670c0 83 67 150 150 150h84c16 0 30 -17 30 -32c0 -16 -9 -32 -30 -32h-84c-47 0 -87 -40 -87 -86v-667c0 -47 40 -87 87 -87zM676 -9c-6 -5 -14 -8 -22 -8c-7 0 -15 3 -21 8
|
||||||
s-8 12 -8 19c0 9 5 20 12 27l233 233h-625c-22 0 -35 14 -35 30c0 17 13 30 35 30h625l-233 233c-7 7 -11 16 -11 25s4 17 11 23c6 5 14 8 21 8c8 0 16 -3 22 -9c14 -13 284 -283 310 -310c-30 -30 -272 -270 -314 -309z" />
|
s-8 12 -8 19c0 9 5 20 12 27l233 233h-625c-22 0 -35 12 -35 30s13 30 35 30h625l-233 233c-7 7 -11 16 -11 25s4 17 11 23c6 5 14 8 21 8c8 0 16 -3 22 -9l310 -310c-30 -30 -314 -309 -314 -309z" />
|
||||||
<glyph glyph-name="more" unicode=""
|
<glyph glyph-name="more" unicode=""
|
||||||
d="M17 307c0 60 49 109 109 109c61 0 110 -49 110 -109s-49 -109 -110 -109c-60 0 -109 49 -109 109zM403 307c0 60 49 109 109 109s109 -49 109 -109s-49 -109 -109 -109s-109 49 -109 109zM788 307c0 60 49 109 110 109c60 0 109 -49 109 -109s-49 -109 -109 -109
|
d="M17 307c0 60 49 109 109 109c61 0 110 -49 110 -109s-49 -109 -110 -109c-60 0 -109 49 -109 109zM403 307c0 60 49 109 109 109s109 -49 109 -109s-49 -109 -109 -109s-109 49 -109 109zM788 307c0 60 49 109 110 109c60 0 109 -49 109 -109s-49 -109 -109 -109
|
||||||
c-61 0 -110 49 -110 109z" />
|
c-61 0 -110 49 -110 109z" />
|
||||||
<glyph glyph-name="promote" unicode="" horiz-adv-x="1028"
|
<glyph glyph-name="promote" unicode="" horiz-adv-x="1028"
|
||||||
d="M993 601c10 -8 11 -18 11 -28v-522c0 -14 -10 -33 -24 -33h-4c0 0 -520 129 -529 129v0c0 -65 -58 -110 -119 -110c-65 0 -120 55 -120 120v44l-128 31v-9c0 -15 -18 -27 -32 -27c-17 0 -31 12 -31 29v184c0 14 17 28 31 28c18 0 32 -11 32 -28v-17l886 215
|
d="M993 601c10 -8 11 -18 11 -28v-523c0 -14 -5 -20 -11 -25c-10 -8 -14 -8 -17 -7l-529 129c0 -65 -58 -110 -119 -110c-65 0 -120 55 -120 120v44l-128 31v-9c0 -15 -18 -27 -32 -27c-17 0 -31 12 -31 29v184c0 14 17 28 31 28c18 0 32 -11 32 -28v-17l886 215
|
||||||
c7 2 18 1 27 -6zM331 92c20 0 55 7 55 58l-113 27v-30c0 -31 27 -55 58 -55zM944 82v450l-864 -211v-31c287 -69 577 -139 864 -208z" />
|
c7 2 18 1 27 -6zM331 92c20 0 55 7 55 58l-113 27v-30c0 -31 27 -55 58 -55zM944 82v450l-864 -211v-31c287 -69 864 -208 864 -208z" />
|
||||||
<glyph glyph-name="video_off" unicode=""
|
<glyph glyph-name="video_off" unicode=""
|
||||||
d="M963 78l-236 106c-10 5 -20 17 -20 31v167c0 14 6 21 20 27l236 106c4 2 9 3 13 3c16 0 31 -14 31 -34v-378c0 -18 -15 -31 -31 -31c-4 0 -9 1 -13 3zM945 153v280l-174 -78v-123zM915 662l-212 -208v-410c0 -17 -14 -31 -31 -31h-409s-110 -110 -113 -112
|
d="M963 78l-236 106c-10 5 -20 17 -20 31v167c0 14 6 21 20 27l236 106c4 2 9 3 13 3c16 0 31 -14 31 -34v-378c0 -18 -15 -31 -31 -31c-4 0 -9 1 -13 3zM945 153v280l-174 -78v-123zM915 662l-212 -208v-410c0 -17 -14 -31 -31 -31h-409s-110 -110 -113 -112
|
||||||
c-5 -4 -12 -6 -18 -6c-10 0 -20 5 -26 13c-4 5 -6 11 -6 17c0 9 5 19 13 27l61 61h-123c-17 0 -31 14 -31 31v526c0 17 14 30 31 30h625c17 0 31 -13 31 -30v-24l160 160c5 5 13 8 20 8c8 0 17 -4 22 -9c8 -8 13 -17 13 -26c0 -6 -2 -12 -7 -17zM81 74h155l406 407v58h-561
|
c-5 -4 -12 -6 -18 -6c-10 0 -20 5 -26 13c-4 5 -6 11 -6 17c0 9 5 19 13 27l61 61h-123c-17 0 -31 14 -31 31v526c0 17 14 30 31 30h625c17 0 31 -13 31 -30v-24l160 160c5 5 13 8 20 8c8 0 17 -4 22 -9c8 -8 13 -17 13 -26c0 -6 -2 -12 -7 -17zM81 74h155l406 407v58h-561
|
||||||
v-465zM642 74v318l-314 -318h314z" />
|
v-465zM642 74v318l-314 -318h314z" />
|
||||||
<glyph glyph-name="user" unicode=""
|
<glyph glyph-name="user" unicode=""
|
||||||
d="M860 85c79 -55 123 -143 123 -239c0 -17 -14 -31 -31 -31h-880c-17 0 -31 14 -31 31c0 96 44 184 123 239c85 58 198 86 348 86s263 -28 348 -86zM106 -123v-4h812c-10 65 -44 120 -95 158c-75 51 -171 75 -311 75s-236 -21 -311 -72c-55 -38 -88 -96 -95 -157zM512 239
|
d="M860 85c79 -55 123 -143 123 -239c0 -17 -14 -31 -31 -31h-880c-17 0 -31 14 -31 31c0 96 37 179 123 239c115 79 198 86 348 86s263 -28 348 -86zM106 -123v-4h812c-10 65 -44 120 -95 158c-75 51 -171 75 -311 75s-236 -21 -311 -72c-55 -38 -88 -96 -95 -157zM512 239
|
||||||
c-147 0 -263 115 -263 273c0 151 112 290 263 290c154 0 263 -136 263 -290c0 -153 -120 -273 -263 -273zM307 515c0 -105 89 -211 202 -211s201 85 201 211c0 118 -78 222 -198 222c-114 0 -205 -110 -205 -222z" />
|
c-147 0 -263 115 -263 273c0 151 112 290 263 290c154 0 263 -136 263 -290c0 -153 -120 -273 -263 -273zM307 515c0 -105 89 -211 202 -211s201 85 201 211c0 118 -78 222 -198 222c-114 0 -205 -110 -205 -222z" />
|
||||||
<glyph glyph-name="up_arrow" unicode=""
|
<glyph glyph-name="up_arrow" unicode=""
|
||||||
d="M659 502c8 -8 12 -18 12 -29c0 -9 -3 -18 -10 -25c-8 -8 -18 -13 -29 -13c-10 0 -20 4 -28 12l-221 222v-816c0 -20 -20 -39 -40 -39s-39 19 -39 39v816l-222 -222c-8 -8 -17 -11 -27 -11s-20 4 -28 11c-8 8 -12 17 -12 27s4 20 12 28l290 286c8 8 17 13 27 13
|
d="M659 502c8 -8 12 -18 12 -29c0 -9 -3 -18 -10 -25c-8 -8 -18 -13 -29 -13c-10 0 -20 4 -28 12l-221 222v-816c0 -20 -20 -39 -40 -39s-39 19 -39 39v816l-222 -222c-8 -8 -17 -11 -27 -11s-20 4 -28 11c-8 8 -12 17 -12 27s4 20 12 28l290 286c8 8 17 13 27 13
|
||||||
@ -65,9 +65,9 @@ d="M58 -205c-24 0 -41 17 -41 41c0 10 3 20 10 27l396 449l-396 439c-8 8 -11 16 -11
|
|||||||
d="M973 802c20 0 34 -14 37 -34v-662c0 -20 -14 -35 -34 -35h-375l222 -201c8 -7 17 -20 17 -33c0 -6 -2 -12 -7 -18s-14 -8 -23 -8c-12 0 -24 4 -32 11l-232 209v-190c0 -19 -11 -36 -32 -36c-17 0 -33 16 -33 36v190c-20 -19 -196 -174 -236 -210c-6 -5 -16 -9 -27 -9
|
d="M973 802c20 0 34 -14 37 -34v-662c0 -20 -14 -35 -34 -35h-375l222 -201c8 -7 17 -20 17 -33c0 -6 -2 -12 -7 -18s-14 -8 -23 -8c-12 0 -24 4 -32 11l-232 209v-190c0 -19 -11 -36 -32 -36c-17 0 -33 16 -33 36v190c-20 -19 -196 -174 -236 -210c-6 -5 -16 -9 -27 -9
|
||||||
c-9 0 -18 2 -23 9c-4 5 -6 11 -6 17c0 11 5 23 16 32l222 198h-376c-20 0 -34 14 -34 34v666c0 20 14 34 34 34h922zM940 136v427h-858v-427h858zM82 631h858v106h-858v-106z" />
|
c-9 0 -18 2 -23 9c-4 5 -6 11 -6 17c0 11 5 23 16 32l222 198h-376c-20 0 -34 14 -34 34v666c0 20 14 34 34 34h922zM940 136v427h-858v-427h858zM82 631h858v106h-858v-106z" />
|
||||||
<glyph glyph-name="listen" unicode=""
|
<glyph glyph-name="listen" unicode=""
|
||||||
d="M1096 310c72 -51 116 -133 116 -218c0 -150 -119 -277 -273 -277c-17 0 -31 14 -31 31v485c0 17 14 31 31 31c31 0 61 -7 92 -17c0 227 -233 388 -416 388c-190 0 -393 -146 -417 -375c31 10 61 17 92 17c17 0 31 -13 31 -30v-485c0 -17 -14 -31 -31 -31
|
d="M911 310c62 -44 96 -109 96 -180c0 -123 -99 -229 -225 -229c-14 0 -24 10 -24 24v402c0 14 10 24 24 24c24 0 51 -6 75 -13c-10 170 -140 307 -311 324c-11 1 -21 1 -31 1c-178 0 -332 -135 -348 -315c24 7 51 14 75 14c14 0 24 -11 24 -24v-403c0 -14 -10 -24 -24 -24
|
||||||
c-89 0 -171 41 -222 113c-35 49 -52 104 -52 160c0 85 40 169 114 225c0 263 222 475 485 475c266 0 481 -222 481 -492zM256 -106v416c-89 -14 -160 -85 -174 -174c-2 -12 -3 -24 -3 -36c0 -99 73 -188 177 -206zM973 -123c89 17 160 85 174 177c2 12 3 24 3 36
|
c-75 0 -143 34 -184 92c-30 41 -44 88 -44 134c0 71 33 142 95 187c7 219 185 393 403 396c222 -3 399 -184 399 -410zM215 -34v344c-72 -10 -133 -68 -143 -143c-2 -10 -3 -20 -3 -30c0 -81 60 -156 146 -171zM809 -51c75 17 133 71 143 150c2 10 3 20 3 30
|
||||||
c0 100 -73 189 -177 207v-420z" />
|
c0 81 -60 156 -146 171v-351z" />
|
||||||
<glyph glyph-name="left_arrow" unicode=""
|
<glyph glyph-name="left_arrow" unicode=""
|
||||||
d="M481 -188c-10 0 -20 4 -30 14l-427 486l430 480c7 9 18 13 29 13c10 0 21 -3 29 -10c9 -7 13 -18 13 -29c0 -10 -3 -21 -10 -29l-385 -425l382 -435c7 -8 10 -18 10 -27c0 -10 -4 -21 -13 -28c-7 -7 -18 -10 -28 -10z" />
|
d="M481 -188c-10 0 -20 4 -30 14l-427 486l430 480c7 9 18 13 29 13c10 0 21 -3 29 -10c9 -7 13 -18 13 -29c0 -10 -3 -21 -10 -29l-385 -425l382 -435c7 -8 10 -18 10 -27c0 -10 -4 -21 -13 -28c-7 -7 -18 -10 -28 -10z" />
|
||||||
<glyph glyph-name="happy" unicode=""
|
<glyph glyph-name="happy" unicode=""
|
||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Binary file not shown.
Binary file not shown.
0
record-and-playback/core/scripts/README
Executable file → Normal file
0
record-and-playback/core/scripts/README
Executable file → Normal file
0
record-and-playback/core/scripts/bigbluebutton.yml
Executable file → Normal file
0
record-and-playback/core/scripts/bigbluebutton.yml
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user