Merge branch 'testing-version-002' of https://github.com/antobinary/bigbluebutton into meteor-client-whiteboard

This commit is contained in:
Maxim Khlobystov 2014-09-23 08:40:47 -07:00
commit e3f98d488d
22 changed files with 332 additions and 285 deletions

View File

@ -110,16 +110,20 @@ class BigBlueButtonActor(outGW: MessageOutGateway) extends Actor {
for(i <- 0 until arr.length) {
val id = arr(i)
val duration = meetings.get(arr(i)).head.getDuration()
val name = meetings.get(arr(i)).head.getMeetingName()
val recorded = meetings.get(arr(i)).head.getRecordedStatus()
val voiceBridge = meetings.get(arr(i)).head.getVoiceBridgeNumber()
var info = new MeetingInfo(id, name, recorded)
var info = new MeetingInfo(id, name, recorded, voiceBridge, duration)
resultArray(i) = info
//remove later
println("for a meeting:" + id)
println("Meeting Name = " + meetings.get(id).head.getMeetingName())
println("isRecorded = " + meetings.get(id).head.getRecordedStatus())
println("voiceBridge = " + voiceBridge)
println("duration = " + duration)
//send the users
this ! (new GetUsers(id, "nodeJSapp"))

View File

@ -29,6 +29,10 @@ class MeetingActor(val meetingID: String, val meetingName: String, val recorded:
var muted = false;
var meetingEnded = false
def getDuration():Long = {
duration
}
def getMeetingName():String = {
meetingName
}
@ -36,6 +40,10 @@ class MeetingActor(val meetingID: String, val meetingName: String, val recorded:
def getRecordedStatus():Boolean = {
recorded
}
def getVoiceBridgeNumber():String = {
voiceBridge
}
val TIMER_INTERVAL = 30000
var hasLastWebUserLeft = false

View File

@ -104,4 +104,4 @@ case class MeetingPasswords(moderatorPass: String, viewerPass: String)
case class MeetingDuration(duration: Int = 0, createdTime: Long = 0,
startTime: Long = 0, endTime: Long = 0)
case class MeetingInfo(meetingID: String, meetingName: String, recorded: Boolean)
case class MeetingInfo(meetingID: String, meetingName: String, recorded: Boolean, voiceBridge: String, duration: Long)

View File

@ -13,9 +13,16 @@ login = (req, resp) ->
serverAndSecret = testapi.serverAndSecret
#use the name from the textbox
console.log "\n\nThe Username passed was=" + JSON.stringify(req.body.name) + "\n\n"
joinParams.fullName = JSON.stringify req.body.name
joinParams.fullName = joinParams.fullName.replace(/['"]/g,'')
console.log "\n\nThe Username passed was=" + JSON.stringify(req.body.name) + "The Meetingname passed was=" + JSON.stringify(req.body.meetingName) + "\n\n"
# grab the username and the meeting name passed in. Strip the surrounding quotes
joinParams.fullName = (JSON.stringify req.body.name)?.replace(/['"]/g,'')
passedMeetingName = (JSON.stringify req.body.meetingName)?.replace(/["]/g,'')
# use the meeting name from the form to [create if not existing and] join the meeting with such name
joinParams.meetingID = passedMeetingName
createParams.name = passedMeetingName
createParams.meetingID = passedMeetingName
#calling createapi
bbbapi.create(createParams, serverAndSecret, {}, (errorOuter, responseOuter, bodyOuter) ->

View File

@ -5,7 +5,8 @@ myModule.controller('MainCtrl', function($scope, $http, $location, $window) {
$scope.postUsername = function() {
var account = {
"name": $scope.username,
"password": 'oOoOoO'
"password": 'oOoOoO',
"meetingName": $scope.meetingName
};
jQuery.getJSON("config.json", function (json) {
$http.post('/login', account).success(function(res) {

View File

@ -9,6 +9,9 @@
<div>
<label>Username:</label>
<input type="text" ng-model="username" placeholder="Enter a name here">
<label>Meeting name:</label>
<input type="text" ng-model="meetingName" placeholder="Enter a meeting name">
<label>(e.x. Demo Meeting)</label>
</div>
<div ng-controller="MainCtrl">
<form ng-submit="postUsername()">

View File

@ -20,6 +20,9 @@
meet?.meetingName
else null
@getTimeOfJoining = ->
Meteor.Users.findOne({"user.userid": getInSession("userId")})?.user?.time_of_joining
# Finds the names of all people the current user is in a private conversation with
# Removes yourself and duplicates if they exist
@getPrivateChatees = ->
@ -123,6 +126,12 @@ Handlebars.registerHelper "isUserSharingAudio", (u) ->
user?.user?.voiceUser?.joined
else return false
Handlebars.registerHelper "isUserListenOnly", (u) ->
if u?
user = Meteor.Users.findOne({userId:u.userid})
user?.user?.listenOnly
else return false
Handlebars.registerHelper "isUserSharingVideo", (u) ->
u.webcam_stream?.length isnt 0
@ -221,7 +230,7 @@ Meteor.methods
username = "#{getInSession("userId")}-bbbID-#{getUsersName()}"
# voicePin = Meteor.Meetings.findOne()?.voiceConf
# voiceBridge = if voicePin? then voicePin else "0"
voiceBridge = "70827"
voiceBridge = Meteor.Meetings.findOne({}).voiceConf # need to know this info for all meetings #TODO
server = null
joinCallback = (message) ->
console.log JSON.stringify message
@ -275,4 +284,5 @@ Meteor.methods
@getCurrentSlideDoc = -> # returns only one document
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
presentationId = currentPresentation?.presentation?.id
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})

View File

@ -35,6 +35,12 @@ Template.header.events
Meteor.call('userLowerHand', getInSession("meetingId"), getInSession("userId"), loweredBy)
"click .whiteboardIcon": (event) ->
toggleWhiteBoard()
"mouseover #navbarMinimizedButton": (event) ->
$("#navbarMinimizedButton").removeClass("navbarMinimizedButtonSmall")
$("#navbarMinimizedButton").addClass("navbarMinimizedButtonLarge")
"mouseout #navbarMinimizedButton": (event) ->
$("#navbarMinimizedButton").removeClass("navbarMinimizedButtonLarge")
$("#navbarMinimizedButton").addClass("navbarMinimizedButtonSmall")
Template.recordingStatus.rendered = ->
$('button[rel=tooltip]').tooltip()
@ -46,10 +52,6 @@ Template.main.helpers
Template.makeButton.rendered = ->
$('button[rel=tooltip]').tooltip()
# Gets called last in main template, just an easy place to print stuff out
Handlebars.registerHelper "doFinalStuff", ->
console.log "-----Doing Final Stuff-----"
# These settings can just be stored locally in session, created at start up
Meteor.startup ->
@SessionAmplify = _.extend({}, Session,

View File

@ -69,11 +69,9 @@
{{> makeButton btn_class="hideNavbarIcon navbarButton" i_class="chevron-up" rel="tooltip" data_placement="bottom" title="Hide Navbar"}}
</div>
</div>
<div class="navbarFiller"></div>
{{else}}
<div id="navbar" class="myNavbarMinimized navbar-default navbar-static-top" role="navigation">
<!-- User wants to hide navbar. The button for bringing the navbar back needs to still be available. Perhaps it should appear/disappear in the future on hover? Something to add later. -->
{{> makeButton btn_class="hideNavbarIcon navbarMinimizedButton" i_class="chevron-down" rel="tooltip" data_placement="bottom" title="Display Navbar"}}
</div>
{{> makeButton id="navbarMinimizedButton" btn_class="hideNavbarIcon navbarMinimizedButtonSmall" i_class="chevron-down" rel="tooltip" data_placement="bottom" title="Display Navbar"}}
{{/if}}
</template>
@ -87,7 +85,6 @@
{{> chatbar id="chat" title="Chat" name="chatbar"}}
<audio id="remote-media" autoplay="autoplay"></audio>
{{> footer}}
{{doFinalStuff}}
</div>
</body>
</template>

View File

@ -1,3 +1,8 @@
bottomEntry{
border: none;
padding-bottom: 0px;
padding-top: 0px;
}
.chat{
list-style: none;
margin: 0px;

View File

@ -19,6 +19,7 @@ body {
border: 1px solid #ccc;
float: left;
height:90%;
margin-top: 10px;
}
.gradientBar{
background: -webkit-linear-gradient(rgb(255,255,255), rgb(182,181,181)); /* For Safari 5.1 to 6.0 */
@ -30,7 +31,6 @@ body {
.lowerHand{}
.mainContainer{
height:100%;
padding-top:60px;
}
.myFooter{
color:black;
@ -58,16 +58,29 @@ body {
margin-right: 2px;
width:40px;
}
.navbarFiller{
height:50px;
width:100%;
}
.navbarIconToggleActive{
border-bottom: 4px solid lightblue;
}
.navbarMinimizedButton{
height:20px;
margin-bottom:0px;
margin-left: 2px;
margin-right: 20px;
margin-top:0px;
width:40px;
#navbarMinimizedButton{
margin-bottom:0px;
margin-left: 2px;
margin-right: 20px;
margin-top:0px;
position: absolute;
right: 0;
top:0;
}
.navbarMinimizedButtonSmall{
height:10px;
width:40px;
}
.navbarMinimizedButtonLarge{
height:50px;
width:40px;
}
.navbarSection{
float:left;

View File

@ -13,7 +13,7 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 90px;
width: 100px;
}
.user-entry tr:nth-child(even) {background-color: rgb(245,245,245);}
.user-entry tr:hover {font-weight: bold;}

View File

@ -5,7 +5,7 @@
chattingWith = getInSession('inChatWith')
if chattingWith isnt "PUBLIC_CHAT"
if chattingWith isnt "PUBLIC_CHAT"
dest = Meteor.Users.findOne("userId": chattingWith)
messageForServer = { # construct message for server
@ -28,97 +28,124 @@
Template.chatInput.events
'click #sendMessageButton': (event) ->
sendMessage()
'keypress #newMessageInput': (event) -> # user pressed a button inside the chatbox
if event.shiftKey and event.which is 13
$("#newMessageInput").append("\n")
return
if event.which is 13 # Check for pressing enter to submit message
sendMessage()
$('#newMessageInput').val("")
return false
Template.chatInput.rendered = ->
$('input[rel=tooltip]').tooltip()
$('button[rel=tooltip]').tooltip()
Template.chatbar.helpers
getChatGreeting: ->
greeting = "Welcome to #{getMeetingName()}!\n\n
For help on using BigBlueButton see these (short) <a href='http://www.bigbluebutton.org/videos/' target='_blank'>tutorial videos</a>.\n\n
To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.\n\n\n
This server is running BigBlueButton #{getInSession 'bbbServerVersion'}."
getChatGreeting: ->
greeting = "Welcome to #{getMeetingName()}!\n\n
For help on using BigBlueButton see these (short) <a href='http://www.bigbluebutton.org/videos/' target='_blank'>tutorial videos</a>.\n\n
To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.\n\n\n
This server is running BigBlueButton #{getInSession 'bbbServerVersion'}."
# This method returns all messages for the user. It looks at the session to determine whether the user is in
#private or public chat. If true is passed, messages returned are from before the user joined. Else, the messages are from after the user joined
getFormattedMessagesForChat: () ->
friend = chattingWith = getInSession('inChatWith') # the recipient(s) of the messages
after = before = greeting = []
# This method returns all messages for the user. It looks at the session to determine whether the user is in
#private or public chat. If true is passed, messages returned are from before the user joined. Else, the messages are from after the user joined
getFormattedMessagesForChat: () ->
friend = chattingWith = getInSession('inChatWith') # the recipient(s) of the messages
after = before = greeting = []
if chattingWith is 'PUBLIC_CHAT' # find all public messages
before = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$lt: String(getInSession("joinedAt"))}}).fetch()
after = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$gt: String(getInSession("joinedAt"))}}).fetch()
if chattingWith is 'PUBLIC_CHAT' # find all public messages
before = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$lt: String(getTimeOfJoining())}}).fetch()
after = Meteor.Chat.find({'message.chat_type': chattingWith, 'message.from_time': {$gt: String(getTimeOfJoining())}}).fetch()
greeting = [
'message':
'message': Template.chatbar.getChatGreeting(),
'from_username': 'System',
'from_time': getTime()
'from_color': '0x3399FF' # A nice blue in hex
]
else
me = getInSession("userId")
after = Meteor.Chat.find({ # find all messages between current user and recipient
'message.chat_type': 'PRIVATE_CHAT',
$or: [{'message.from_userid': me, 'message.to_userid': friend},{'message.from_userid': friend, 'message.to_userid': me}]
}).fetch()
greeting = [
'message':
'message': Template.chatbar.getChatGreeting(),
'from_username': 'System',
'from_time': getTimeOfJoining()
'from_color': '0x3399FF' # A nice blue in hex
]
else
me = getInSession("userId")
after = Meteor.Chat.find({ # find all messages between current user and recipient
'message.chat_type': 'PRIVATE_CHAT',
$or: [{'message.from_userid': me, 'message.to_userid': friend},{'message.from_userid': friend, 'message.to_userid': me}]
}).fetch()
messages = (before.concat greeting).concat after
messages = (before.concat greeting).concat after
getCombinedMessagesForChat: ->
msgs = Template.chatbar.getFormattedMessagesForChat()
prev_time = msgs[0]?.message.from_time
prev_userid = msgs[0]?.message.from_userid
for i in [0...msgs.length]
if i != 0
if prev_userid is msgs[i].message.from_userid
msgs[i].message.from_username = ''
if Template.message.toClockTime(msgs[i].message.from_time) is Template.message.toClockTime(prev_time)
prev_time = msgs[i].message.from_time
msgs[i].message.from_time = null
else
prev_time = msgs[i].message.from_time
else
prev_time = msgs[i].message.from_time
prev_userid = msgs[i].message.from_userid
msgs
Template.message.rendered = -> # When a message has been added and finished rendering, scroll to the bottom of the chat
$('#chatbody').scrollTop($('#chatbody')[0].scrollHeight)
getCombinedMessagesForChat: ->
msgs = Template.chatbar.getFormattedMessagesForChat()
len = msgs.length # get length of messages
i = 0
while i < len # Must be a do while, for loop compiles and stores the length of array which can change inside the loop!
if msgs[i].message.from_userid isnt 'System' # skip system messages
j = i+1 # Start looking at messages right after the current one
while j < len
deleted = false
if msgs[j].message.from_userid isnt 'System' # Ignore system messages
# Check if the time discrepancy between the two messages exceeds window for grouping
if (parseFloat(msgs[j].message.from_time)-parseFloat(msgs[i].message.from_time)) >= 60000 # 60 seconds/1 minute
break # Messages are too far between, so them seperated and stop joining here
if msgs[i].message.from_userid is msgs[j].message.from_userid # Both messages are from the same user
msgs[i].message.message += "\\n#{msgs[j].message.message}" # Combine the messages
msgs.splice(j,1) # Delete the message from the collection
deleted=true
else break # Messages are from different people, move on
#
else break # This is the break point in the chat, don't merge
#
len = msgs.length
++j if not deleted
#
++i
len = msgs.length
msgs
# When chatbar gets rendered, scroll to the bottom
Template.chatbar.rendered = ->
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
false
# Scrolls the message container to the bottom. The number of pixels to scroll down is the height of the container
Handlebars.registerHelper "autoscroll", ->
$('#chatbody').scrollTop($('#chatbody')[0]?.scrollHeight)
false
Template.optionsBar.events
'click .private-chat-user-entry': (event) -> # clicked a user's name to begin private chat
setInSession 'display_chatPane', true
setInSession "inChatWith", @userId
me = getInSession("userId")
'click .private-chat-user-entry': (event) -> # clicked a user's name to begin private chat
setInSession 'display_chatPane', true
setInSession "inChatWith", @userId
me = getInSession("userId")
if Meteor.Chat.find({'message.chat_type': 'PRIVATE_CHAT', $or: [{'message.from_userid': me, 'message.to_userid': @userId},{'message.from_userid': @userId, 'message.to_userid': me}]}).fetch().length is 0
messageForServer =
"message": "#{getUsersName()} has joined private chat with #{@user.name}."
"chat_type": "PRIVATE_CHAT"
"from_userid": me
"from_username": getUsersName()
"from_tz_offset": "240"
"to_username": @user.name
"to_userid": @userId
"from_lang": "en"
"from_time": getTime()
"from_color": "0"
Meteor.call "sendChatMessagetoServer", getInSession("meetingId"), messageForServer
if Meteor.Chat.find({'message.chat_type': 'PRIVATE_CHAT', $or: [{'message.from_userid': me, 'message.to_userid': @userId},{'message.from_userid': @userId, 'message.to_userid': me}]}).fetch().length is 0
messageForServer =
"message": "#{getUsersName()} has joined private chat with #{@user.name}."
"chat_type": "PRIVATE_CHAT"
"from_userid": me
"from_username": getUsersName()
"from_tz_offset": "240"
"to_username": @user.name
"to_userid": @userId
"from_lang": "en"
"from_time": getTime()
"from_color": "0"
Meteor.call "sendChatMessagetoServer", getInSession("meetingId"), messageForServer
Template.optionsBar.rendered = ->
$('div[rel=tooltip]').tooltip()
$('div[rel=tooltip]').tooltip()
Template.optionsFontSize.events
"click .fontSizeSelector": (event) ->
selectedFontSize = parseInt(event.target.id)
if selectedFontSize
setInSession "messageFontSize", selectedFontSize
else setInSession "messageFontSize", 12
"click .fontSizeSelector": (event) ->
selectedFontSize = parseInt(event.target.id)
if selectedFontSize
setInSession "messageFontSize", selectedFontSize
else setInSession "messageFontSize", 12
Template.tabButtons.events
'click .close': (event) -> # user closes private chat
@ -157,31 +184,31 @@ Template.tabButtons.helpers
button
Template.message.helpers
activateBreakLines: (str) ->
res = str.replace /\n/gim, '<br/>'
res = res.replace /\r/gim, '<br/>'
# make links received from Flash client clickable in HTML
toClickable: (str) ->
res = str.replace /<a href='event:/gim, "<a target='_blank' href='"
res = res.replace /<a href="event:/gim, '<a target="_blank" href="'
activateBreakLines: (str) ->
res = str.replace /\\n/gim, '<br/>'
res = res.replace /\r/gim, '<br/>'
# make links received from Flash client clickable in HTML
toClickable: (str) ->
res = str.replace /<a href='event:/gim, "<a target='_blank' href='"
res = res.replace /<a href="event:/gim, '<a target="_blank" href="'
toClockTime: (epochTime) ->
if epochTime is null
return ""
local = new Date()
offset = local.getTimezoneOffset()
epochTime = epochTime - offset * 60000 # 1 min = 60 s = 60,000 ms
dateObj = new Date(epochTime)
hours = dateObj.getUTCHours()
minutes = dateObj.getUTCMinutes()
if minutes < 10
minutes = "0" + minutes
hours + ":" + minutes
toClockTime: (epochTime) ->
if epochTime is null
return ""
local = new Date()
offset = local.getTimezoneOffset()
epochTime = epochTime - offset * 60000 # 1 min = 60 s = 60,000 ms
dateObj = new Date(epochTime)
hours = dateObj.getUTCHours()
minutes = dateObj.getUTCMinutes()
if minutes < 10
minutes = "0" + minutes
hours + ":" + minutes
sanitizeAndFormat: (str) ->
# First, replace replace all tags with the ascii equivalent (excluding those involved in anchor tags)
res = str.replace(/&/g, '&amp;').replace(/<(?![au\/])/g, '&lt;').replace(/\/([^au])>/g, '$1&gt;').replace(/([^=])"(?!>)/g, '$1&quot;');
res = Template.message.toClickable res
res = Template.message.activateBreakLines res
sanitizeAndFormat: (str) ->
# First, replace replace all tags with the ascii equivalent (excluding those involved in anchor tags)
res = str.replace(/&/g, '&amp;').replace(/<(?![au\/])/g, '&lt;').replace(/\/([^au])>/g, '$1&gt;').replace(/([^=])"(?!>)/g, '$1&quot;');
res = Template.message.toClickable res
res = Template.message.activateBreakLines res

View File

@ -7,8 +7,11 @@
<div id="chatbody">
<ul class="chat">
{{#each getCombinedMessagesForChat}}
<li {{messageFontSize}}>{{> message}}</li>
{{#if message}}
<li {{messageFontSize}}>{{> message}}</li>
{{/if}}
{{/each}}
<bottomEntry></bottomEntry> <!-- Intentionally blank, fixes issue what last message being cut off while scrolling -->
</ul>
</div>
<div class="panel-footer">{{> chatInput}}</div>
@ -21,7 +24,7 @@
<template name="chatInput">
<div class="chat-input-wrapper">
<input type="text" id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message" />
<textarea id="newMessageInput" placeholder="Write a message..." rel="tooltip" data-placement="top" title="Write a new message"></textarea>
<button type="submit" id="sendMessageButton" class="btn" rel="tooltip" data-placement="top" title="Click to send your message">
Send
</button>
@ -39,7 +42,7 @@
<tr>
<td>
{{#if message.from_username}}
<div class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{message.from_username}}">
<div {{messageFontSize}} class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{message.from_username}}">
<strong>{{message.from_username}}</strong>
</div>
{{/if}}
@ -50,6 +53,7 @@
</tr>
</table>
<div style="color:{{colourToHex message.from_color}}">{{{sanitizeAndFormat message.message}}}</div> <!-- Messages must be safely filtered and stripped -->
{{autoscroll}} <!-- Scroll to the bottom after inserting a new message -->
</template>
<!-- Displays the list of options available -->

View File

@ -1,27 +1,32 @@
<template name="displayUserIcons">
<td>{{#if isUserSharingVideo user}}
<span class="userListSettingIcon glyphicon glyphicon-facetime-video"></span>
<span class="userListSettingIcon glyphicon glyphicon-facetime-video" rel="tooltip" data-placement="bottom" title="{{user.name}} is sharing their webcam"></span>
{{/if}}</td>
<!-- Should use much more noticeable icons than just bootstrap's volume-up & volume-down to differentiate talking but it is good for now -->
<td>{{#if isUserSharingAudio user}}
{{#if isUserMuted user}}
<span class="userListSettingIcon glyphicon glyphicon-volume-off"></span>
<span class="userListSettingIcon glyphicon glyphicon-volume-off" rel="tooltip" data-placement="bottom" title="{{user.name}} is muted"></span>
{{else}}
{{#if isUserTalking user}}
<span class="userListSettingIcon glyphicon glyphicon-volume-up"></span>
<span class="userListSettingIcon glyphicon glyphicon-volume-up" rel="tooltip" data-placement="bottom" title="{{user.name}} is talking"></span>
{{else}}
<span class="userListSettingIcon glyphicon glyphicon-volume-down"></span>
<span class="userListSettingIcon glyphicon glyphicon-volume-down" rel="tooltip" data-placement="bottom" title="{{user.name}} is not talking"></span>
{{/if}}
{{/if}}
{{/if}}</td>
{{/if}}
<!-- listen only: -->
{{#if isUserListenOnly user}}
<span class="userListSettingIcon glyphicon glyphicon-headphones" title="Listening only"></span>
{{/if}}
</td>
<td>{{#if user.presenter}}<span class="userListSettingIcon glyphicon glyphicon-picture"></span>{{/if}}</td>
<td>{{#if user.presenter}}<span class="userListSettingIcon glyphicon glyphicon-picture" rel="tooltip" data-placement="bottom" title="{{user.name}} is the presenter"></span>{{/if}}</td>
<td>{{#if user.raise_hand}}<span class="userListSettingIcon glyphicon glyphicon-hand-up"></span>{{/if}}</td>
<td>{{#if user.raise_hand}}<span class="userListSettingIcon glyphicon glyphicon-hand-up" rel="tooltip" data-placement="bottom" title="{{user.name}} has raised their hand"></span>{{/if}}</td>
</template>
<template name="usernameEntry"> <!-- A template now because more information be added/styled -->
<template name="usernameEntry">
<td class="userNameContainer">
{{#if isCurrentUser userId}}
<p class="userNameEntry" rel="tooltip" data-placement="bottom" title="{{user.name}} (you)">

View File

@ -41,7 +41,8 @@ Template.slide.helpers
shapeType = shapeInfo?.type
if shapeType isnt "text"
for num in [0..3] # the coordinates must be in the range 0 to 1
len = shapeInfo.points.length
for num in [0..len] # the coordinates must be in the range 0 to 1
shapeInfo?.points[num] = shapeInfo?.points[num] / 100
wpm.makeShape(shapeType, shapeInfo)
wpm.updateShape(shapeType, shapeInfo)
@ -54,7 +55,8 @@ Template.shape.rendered = ->
shapeType = shapeInfo?.type
if shapeType isnt "text"
for num in [0..3] # the coordinates must be in the range 0 to 1
len = shapeInfo.points.length
for num in [0..len] # the coordinates must be in the range 0 to 1
shapeInfo.points[num] = shapeInfo.points[num] / 100
wpm = Template.slide.whiteboardPaperModel

View File

@ -14,9 +14,6 @@ class @WhiteboardLineModel extends WhiteboardToolModel
# format: svg path, stroke color, thickness
@definition = ["", "#000", "0px"]
# @lineX = null
# @lineY = null
# Creates a line in the paper
# @param {number} x the x value of the line start point as a percentage of the original width
# @param {number} y the y value of the line start point as a percentage of the original height
@ -63,36 +60,12 @@ class @WhiteboardLineModel extends WhiteboardToolModel
y2 = info.points[3]
if @obj?
path = @_buildPath(info.points)
# if adding points from the pencil
if _.isBoolean(info.adding)
add = info.adding
@definition[0] = path
pathPercent = "L" + x1 + " " + y1
@definition.data[0] += pathPercent
x1 = x1 * @gw + @xOffset
y1 = y1 * @gh + @yOffset
# if adding to the line
if add
path = @obj.attrs.path + "L" + x1 + " " + y1
@obj.attr path: path
# if simply updating the last portion (for drawing a straight line)
else
@obj.attrs.path.pop()
path = @obj.attrs.path.join(" ")
path = path + "L" + x1 + " " + y1
@obj.attr path: path
# adding lines from the line tool
else
path = @_buildPath(x1, y1, x2, y2)
@definition[0] = path
path = @_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset)
@obj.attr path: path
path = @_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset)
@obj.attr path: path
# Draw a line on the paper
# @param {number,string} x1 1) the x value of the first point
@ -110,19 +83,19 @@ class @WhiteboardLineModel extends WhiteboardToolModel
draw: (x1, y1, x2, y2, colour, thickness) ->
# if the drawing is from the pencil tool, it comes as a path first
if _.isString(x1)
colour = y1
thickness = x2
path = x1
# if _.isString(x1)
# colour = y1
# thickness = x2
# path = x1
# if the drawing is from the line tool, it comes with two points
else
path = @_buildPath(x1, y1, x2, y2)
# # if the drawing is from the line tool, it comes with two points
# else
# path = @_buildPath(points)
line = @paper.path(@_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset))
line.attr Utils.strokeAndThickness(colour, thickness)
line.attr({"stroke-linejoin": "round"})
line
# line = @paper.path(@_scaleLinePath(path, @gw, @gh, @xOffset, @yOffset))
# line.attr Utils.strokeAndThickness(colour, thickness)
# line.attr({"stroke-linejoin": "round"})
# line
# When dragging for drawing lines starts
# @param {number} x the x value of the cursor
@ -185,8 +158,19 @@ class @WhiteboardLineModel extends WhiteboardToolModel
# [ @_scaleLinePath(path.join(","), 1 / @gw, 1 / @gh),
# @currentColour, @currentThickness ]
_buildPath: (x1, y1, x2, y2) ->
"M#{x1} #{y1}L#{x2} #{y2}"
_buildPath: (points) ->
path = ""
if points and points.length >= 2
path += "M #{points[0]} #{points[1]}"
i = 2
while i < points.length
path += "L#{points[i]} #{points[i + 1]}"
i += 2
path += "Z"
path
# Scales a path string to fit within a width and height of the new paper size
# @param {number} w width of the shape as a percentage of the original width

View File

@ -9,24 +9,21 @@ class @WhiteboardPaperModel
# all slides in the presentation indexed by url
@slides = {}
# the slide being shown
@currentSlide = null
@fitToPage = true
@panX = null
@panY = null
@current = {}
# the slide being shown
@current.slide = null
# a raphaeljs set with all the shapes in the current slide
@currentShapes = null
@current.shapes = null
# a list of shapes as passed to this client when it receives `all_slides`
# (se we are able to redraw the shapes whenever needed)
@currentShapesDefinitions = []
# pointers to the current shapes being drawn
@currentLine = null
@currentRect = null
@currentEllipse = null
@currentTriangle = null
@currentText = null
@current.shapeDefinitions = []
@zoomLevel = 1
@shiftPressed = false
@ -100,7 +97,7 @@ class @WhiteboardPaperModel
# Re-add the images to the paper that are found
# in the slides array (an object of urls and dimensions).
rebuild: ->
@currentSlide = null
@current.slide = null
for url of @slides
if @slides.hasOwnProperty(url)
@addImageToPaper url, @slides[url].getWidth(), @slides[url].getHeight()
@ -116,13 +113,13 @@ class @WhiteboardPaperModel
# will change quite a bit
# slides
slidesTmp = _.clone(@slides)
urlTmp = @currentSlide
urlTmp = @current.slide
@removeAllImagesFromPaper()
@slides = slidesTmp
@rebuild()
@showImageFromPaper(urlTmp?.url)
# drawings
tmp = _.clone(@currentShapesDefinitions)
tmp = _.clone(@current.shapeDefinitions)
@clearShapes()
@drawListOfShapes(tmp)
@ -167,10 +164,10 @@ class @WhiteboardPaperModel
# y-offset from top left corner as percentage of original height of paper
@slides[url] = new WhiteboardSlideModel(img.id, url, img, originalWidth, originalHeight, sw, sh, cx, cy)
unless @currentSlide?
unless @current.slide?
img.toBack()
@currentSlide = @slides[url]
else if @currentSlide.url is url
@current.slide = @slides[url]
else if @current.slide.url is url
img.toBack()
else
img.hide()
@ -191,7 +188,7 @@ class @WhiteboardPaperModel
@raphaelObj.getById(@slides[url]?.getId())?.remove()
#@trigger('paper:image:removed', @slides[url].getId()) # TODO do we need this?
@slides = {}
@currentSlide = null
@current.slide = null
# Shows an image from the paper.
# The url must be in the slides array.
@ -199,16 +196,16 @@ class @WhiteboardPaperModel
showImageFromPaper: (url) ->
# TODO: temporary solution
url = @_slideUrl(url)
if not @currentSlide? or (@slides[url]? and @currentSlide.url isnt url)
@_hideImageFromPaper(@currentSlide.url) if @currentSlide?
if not @current.slide? or (@slides[url]? and @current.slide.url isnt url)
@_hideImageFromPaper(@current.slide.url) if @current.slide?
next = @_getImageFromPaper(url)
if next
next.show()
next.toFront()
@currentShapes.forEach (element) ->
@current.shapes.forEach (element) ->
element.toFront()
@cursor.toFront()
@currentSlide = @slides[url]
@current.slide = @slides[url]
# Updates the paper from the server values.
# @param {number} cx_ the x-offset value as a percentage of the original width
@ -256,14 +253,14 @@ class @WhiteboardPaperModel
@currentTool = tool
console.log "setting current tool to", tool
switch tool
when "path", "line"
when "line"
@cursor.undrag()
@currentLine = @_createTool(tool)
@cursor.drag(@currentLine.dragOnMove, @currentLine.dragOnStart, @currentLine.dragOnEnd)
when "rect"
@current.line = @_createTool(tool)
@cursor.drag(@current.line.dragOnMove, @current.line.dragOnStart, @current.line.dragOnEnd)
when "rectangle"
@cursor.undrag()
@currentRect = @_createTool(tool)
@cursor.drag(@currentRect.dragOnMove, @currentRect.dragOnStart, @currentRect.dragOnEnd)
@current.rectangle = @_createTool(tool)
@cursor.drag(@current.rectangle.dragOnMove, @current.rectangle.dragOnStart, @current.rectangle.dragOnEnd)
# TODO: the shapes below are still in the old format
# when "panzoom"
@ -333,15 +330,15 @@ class @WhiteboardPaperModel
# Draws an array of shapes to the paper.
# @param {array} shapes the array of shapes to draw
drawListOfShapes: (shapes) ->
@currentShapesDefinitions = shapes
@currentShapes = @raphaelObj.set()
@current.shapeDefinitions = shapes
@current.shapes = @raphaelObj.set()
for shape in shapes
shapeType = shape?.shape?.shape_type
dataBlock = shape?.shape?.shape
data = if _.isString(dataBlock) then JSON.parse(dataBlock) else dataBlock
tool = @_createTool(shapeType)
if tool?
@currentShapes.push tool.draw.apply(tool, data)
@current.shapes.push tool.draw.apply(tool, data)
else
console.log "shape not recognized at drawListOfShapes", shape
@ -355,8 +352,8 @@ class @WhiteboardPaperModel
# Clear all shapes from this paper.
clearShapes: ->
if @currentShapes?
@currentShapes.forEach (element) ->
if @current.shapes?
@current.shapes.forEach (element) ->
element.remove()
@currentShapes = []
@currentShapesDefinitions = []
@ -368,52 +365,20 @@ class @WhiteboardPaperModel
# Updated a shape `shape` with the data in `data`.
# TODO: check if the objects exist before calling update, if they don't they should be created
updateShape: (shape, data) ->
switch shape
when "line"
@currentLine.update(data)
when "rectangle"
@currentRect.update(data)
when "ellipse"
@currentEllipse.update(data)
when "triangle"
@currentTriangle.update(data)
when "text"
#@currentText.update.apply(@currentText, data)
@currentText.update(data)
else
console.log "shape not recognized at updateShape", shape
@current[shape].update(data)
# Make a shape `shape` with the data in `data`.
makeShape: (shape, data) ->
tool = null
switch shape
when "path", "line"
@currentLine = @_createTool(shape)
toolModel = @currentLine
tool = @currentLine.make(data)
when "rectangle"
@currentRect = @_createTool(shape)
toolModel = @currentRect
tool = @currentRect.make(data)
when "ellipse"
@currentEllipse = @_createTool(shape)
toolModel = @currentEllipse
tool = @currentEllipse.make(data)
when "triangle"
@currentTriangle = @_createTool(shape)
toolModel = @currentTriangle
tool = @currentTriangle.make(data)
when "text"
@currentText = @_createTool(shape)
toolModel = @currentText
#tool = @currentText.make.apply(@currentText, data)
tool = @currentText.make(data)
else
console.log "shape not recognized at makeShape", shape
@current[shape] = @_createTool(shape)
toolModel = @current[shape]
tool = @current[shape].make(data)
if tool?
@currentShapes ?= @raphaelObj.set()
@currentShapes.push(tool)
@currentShapesDefinitions.push(toolModel.getDefinition())
@current.shapes ?= @raphaelObj.set()
@current.shapes.push(tool)
@current.shapeDefinitions.push(toolModel.getDefinition())
# Update the cursor position on screen
# @param {number} x the x value of the cursor as a percentage of the width
@ -448,8 +413,8 @@ class @WhiteboardPaperModel
#get the actual size of the slide, depending on the limiting factor (container width or container height)
actualWidth = @currentSlide.displayWidth
actualHeight = @currentSlide.displayHeight
actualWidth = @current.slide.displayWidth
actualHeight = @current.slide.displayHeight
#console.log("actualWidth:" + actualWidth + " actualHeight: " + actualHeight)
#calculate parameters to pass
@ -468,7 +433,6 @@ class @WhiteboardPaperModel
#set parameters to raphael viewbox
@raphaelObj.setViewBox(newXPos , newyPos, newWidth , newHeight , true)
# update the rectangle elements which create the border when page is zoomed
@borders.left.attr( {width:newXPos, height: @containerHeight} )
@ -743,17 +707,19 @@ class @WhiteboardPaperModel
@shiftPressed = false
_currentSlideDimensions: ->
if @currentSlide? then @currentSlide.getDimensions() else [0, 0]
if @current.slide? then @current.slide.getDimensions() else [0, 0]
_currentSlideOriginalDimensions: ->
if @currentSlide? then @currentSlide.getOriginalDimensions() else [0, 0]
if @current.slide? then @current.slide.getOriginalDimensions() else [0, 0]
_currentSlideOffsets: ->
if @currentSlide? then @currentSlide.getOffsets() else [0, 0]
if @current.slide? then @current.slide.getOffsets() else [0, 0]
# Wrapper method to create a tool for the whiteboard
_createTool: (type) ->
switch type
when "pencil"
model = WhiteboardLineModel
when "path", "line"
model = WhiteboardLineModel
when "rectangle"

View File

@ -1,6 +1,6 @@
Meteor.methods
addChatToCollection: (meetingId, messageObject) ->
# manually convert time from 1.408645053653E12 to 1408645053653 if necessary
# manually convert time from 1.408645053653E12 to 1408645053653 if necessary (this is the time_from that the Flash client outputs)
messageObject.from_time = (messageObject.from_time).toString().split('.').join("").split("E")[0]
entry =
meetingId: meetingId

View File

@ -29,7 +29,9 @@ Meteor.methods
console.log "added textShape id =[#{id}]:#{shapeObject.id} in #{meetingId} || now there are #{numShapesOnSlide} shapes on the slide"
else
if shapeObject?.status is "DRAW_END" #the mouse button was released - the drawing is complete
# the mouse button was released - the drawing is complete
# TODO: pencil messages currently don't send draw_end and are labeled all as DRAW_START
if shapeObject?.status is "DRAW_END" or (shapeObject?.status is "DRAW_START" and shapeObject?.shape_type is "pencil")
entry =
meetingId: meetingId
whiteboardId: whiteboardId

View File

@ -39,6 +39,7 @@ Meteor.methods
extern_userid: user.extern_userid
permissions: user.permissions
locked: user.locked
time_of_joining: user.timeOfJoining
voiceUser:
web_userid: user.voiceUser.web_userid
callernum: user.voiceUser.callernum

View File

@ -168,48 +168,37 @@ class Meteor.RedisPubSub
]
unless message.header?.name in ignoredEventTypes
#console.log "\nchannel=" + channel
#console.log "correlationId=" + correlationId if correlationId?
console.log "eventType=" + message.header?.name #+ "\n"
#log.debug({ pattern: pattern, channel: channel, message: message}, "Received a message from redis")
console.log jsonMsg
if message.header?.name is 'user_voice_talking_message'
u = Meteor.Users.findOne({'userId': message.payload?.user?.userid, 'meetingId': meetingId})
if u?
if not u?.user?.voiceUser?.muted
console.log "setting talking to #{message?.payload?.user?.voiceUser?.talking}"
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.talking':message?.payload?.user?.voiceUser?.talking}})
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.joined':true}})
else
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.talking':false}})
# handle voice events
if message.header?.name in ['user_left_voice_message', 'user_joined_voice_message', 'user_voice_talking_message', 'user_voice_muted_message']
voiceUser = message.payload?.user?.voiceUser
@updateVoiceUser(meetingId, voiceUser)
if message.header?.name is 'user_voice_muted_message'
u = Meteor.Users.findOne({'userId': message.payload?.user?.userid, 'meetingId': meetingId})
console.log "\n\n\n got a muted event and the user is #{u}"
if u?
# make sure the user is not currently in talking mode
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.talking': false}})
# update to muted
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.muted':message?.payload?.user?.voiceUser?.muted}})
else console.log "ERROR!! did not find a matching user to mute!!"
# listen only
if message.header?.name is 'user_listening_only'
u = Meteor.Users.findOne({userId: message.payload?.userid, meetingId: meetingId})
Meteor.Users.update({_id:u._id}, {$set: {'user.listenOnly':message.payload?.listen_only}})
# most likely we don't need to ensure that the user's voiceUser's {talking, joined, muted, locked} are false by default #TODO?
if message.header?.name is "get_all_meetings_reply"
console.log "Let's store some data for the running meetings so that when an HTML5 client joins everything is ready!"
listOfMeetings = message.payload?.meetings
for meeting in listOfMeetings
# we currently do not have voice_conf or duration in this message.
Meteor.call("addMeetingToCollection", meeting.meetingID, meeting.meetingName, meeting.recorded, "", "")
Meteor.call("addMeetingToCollection", meeting.meetingID, meeting.meetingName, meeting.recorded, meeting.voiceBridge, meeting.duration)
if message.header?.name is "get_users_reply" and message.payload?.requester_id is "nodeJSapp"
unless Meteor.Meetings.findOne({MeetingId: message.payload?.meeting_id})?
users = message.payload?.users
for user in users
user.timeOfJoining = message.header?.current_time # TODO this might need to be removed
Meteor.call("addUserToCollection", meetingId, user)
if message.header?.name is "user_joined_message"
user = message.payload.user
user.timeOfJoining = message.header?.current_time
Meteor.call("addUserToCollection", meetingId, user)
if message.header?.name is "user_left_message"
@ -218,12 +207,16 @@ class Meteor.RedisPubSub
Meteor.call("removeUserFromCollection", meetingId, userId)
if message.header?.name is "get_chat_history_reply" and message.payload?.requester_id is "nodeJSapp"
unless Meteor.Meetings.findOne({MeetingId: message.payload?.meeting_id})? # TODO check if MeetingId or meetingId!!
unless Meteor.Meetings.findOne({MeetingId: message.payload?.meeting_id})?
for chatMessage in message.payload?.chat_history
Meteor.call("addChatToCollection", meetingId, chatMessage)
if message.header?.name is "send_public_chat_message" or message.header?.name is "send_private_chat_message"
messageObject = message.payload?.message
# use current_time instead of message.from_time so that the chats from Flash and HTML5 have uniform times
messageObject.from_time = message.header?.current_time
Meteor.call("addChatToCollection", meetingId, messageObject)
if message.header?.name is "meeting_created_message"
@ -349,6 +342,18 @@ class Meteor.RedisPubSub
unless message.header?.name is "disconnect_all_users_message"
Meteor.call("removeMeetingFromCollection", meetingId)
#update a voiceUser
updateVoiceUser: (meetingId, voiceUserObject) ->
console.log "I am updating the voiceUserObject with the following: " + JSON.stringify voiceUserObject
u = Meteor.Users.findOne({userId: voiceUserObject?.web_userid, meetingId: meetingId})
if u?
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.talking':voiceUserObject?.talking}})# talking
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.joined':voiceUserObject?.joined}})# joined
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.locked':voiceUserObject?.locked}})# locked
Meteor.Users.update({_id:u._id}, {$set: {'user.voiceUser.muted':voiceUserObject?.muted}})# muted
else
console.log "ERROR! did not find such voiceUser!"
# message should be an object
publish: (channel, message) ->
console.log "Publishing channel=#{channel}, message=#{JSON.stringify(message)}"
@ -385,3 +390,4 @@ class Meteor.RedisPubSub
"payload": {} # I need this, otherwise bbb-apps won't recognize the message
@pubClient.publish(Meteor.config.redis.channels.toBBBApps.meeting, JSON.stringify (message))