improvements to active meetings

This commit is contained in:
Josh 2017-07-04 11:52:35 -04:00
parent 44e2cb7027
commit 27f6076954
8 changed files with 162 additions and 39 deletions

View File

@ -18,6 +18,7 @@
// the landing page using custom Actioncable events.
var MEETINGS = {}
var WAITING = {}
var LOADING_DELAY = 1750 // milliseconds.
var updatePreviousMeetings = function(){
@ -29,19 +30,44 @@ var updatePreviousMeetings = function(){
});
}
var handleUser = function(data, n){
var addUser = function(data){
if(data['role'] == 'MODERATOR'){
MEETINGS[data['meeting']]['moderators'] += n
MEETINGS[data['meeting']]['moderators'].push(data['user'])
} else {
MEETINGS[data['meeting']]['participants'] += n
MEETINGS[data['meeting']]['participants'].push(data['user'])
}
updateMeetingText(MEETINGS[data['meeting']])
}
var updateMeetingText = function(meeting){
$('#' + meeting['name'].replace(' ', '_')).html('<a>' + meeting['name'] + '</a> <i>(' +
meeting['participants'] + ((meeting['participants'] == 1) ? ' user, ' : ' users, ') +
meeting['moderators'] + ((meeting['moderators'] == 1) ? ' mod)' : ' mods)'))
var removeUser = function(data){
if(data['role'] == 'MODERATOR'){
MEETINGS[data['meeting']]['moderators'].splice(MEETINGS[data['meeting']]['moderators'].indexOf(data['user']), 1);
} else {
MEETINGS[data['meeting']]['participants'].splice(MEETINGS[data['meeting']]['participants'].indexOf(data['user']), 1);
}
updateMeetingText(MEETINGS[data['meeting']])
}
var updateMeetingText = function(m){
if(m.hasOwnProperty('moderators')){
var body = '<a>' + m['name'] + '</a><i>: ' + m['moderators'].join('(mod), ') + (m['moderators'].length > 0 ? '(mod)' : '') +
(m['participants'].length > 0 && m['moderators'].length != 0 ? ', ' : '') + m['participants'].join(', ') + '</i>'
} else {
var body = '<a>' + m['name'] + '</a><i> (not yet started): ' +
m['users'].join(', ') + '</i>'
}
if($('#' + m['name'].replace(' ', '_')).length == 0){
var meeting_item = $('<li id = ' + m['name'].replace(' ', '_') + '>' + body + '</li>')
$('.actives').append(meeting_item);
// Set up join on click.
meeting_item.click(function(){
joinMeeting(m['name']);
});
} else {
$('#' + m['name'].replace(' ', '_')).html(body)
}
}
var initialPopulate = function(){
@ -49,21 +75,48 @@ var initialPopulate = function(){
var chopped = window.location.href.split('/')
if (!window.location.href.includes('rooms') || chopped[chopped.length - 2] == $('body').data('current-user')) { return; }
$.get((window.location.href + '/request').replace('#', ''), function(data){
var meetings = data['meetings']
var meetings = data['active']['meetings']
var waiting = data['waiting']
jQuery.each(waiting[$('body').data('current-user')], function(name, users){
WAITING[name] = {'name': name,
'users': users}
updateMeetingText(WAITING[name])
});
for(var i = 0; i < meetings.length; i++){
// Make sure the meeting actually belongs to the current user.
if(meetings[i]['metadata']['room-id'] != $('body').data('current-user')) { continue; }
var name = meetings[i]['meetingName']
var participants = parseInt(meetings[i]['participantCount'])
var moderators = parseInt(meetings[i]['moderatorCount'])
var participants = []
var moderators = []
var attendees;
if(meetings[i]['attendees']['attendee'] instanceof Array){
attendees = meetings[i]['attendees']['attendee']
} else {
attendees = [meetings[i]['attendees']['attendee']]
}
jQuery.each(attendees, function(i, attendee){
if(attendee['role'] == "MODERATOR"){
moderators.push(attendee['fullName'])
} else {
participants.push(attendee['fullName'])
}
});
// Create meeting.
MEETINGS[name] = {'name': name,
'participants': participants - moderators,
'participants': participants,
'moderators': moderators}
if(isPreviouslyJoined(name)){
renderActiveMeeting(MEETINGS[name])
updateMeetingText(MEETINGS[name])
}
}
}).done(function(){
// Remove from previous meetings if they are active.
updatePreviousMeetings();
@ -81,18 +134,6 @@ var isPreviouslyJoined = function(meeting){
return joinedMeetings.split(',').indexOf(meeting) >= 0
}
var renderActiveMeeting = function(m){
var meeting_item = $('<li id = ' + m['name'].replace(' ', '_') + '><a>' + m['name'] + '</a>' +
' <i>(' + m['participants'] + ((m['participants'] == 1) ? ' user, ' : ' users, ') +
m['moderators'] + ((m['moderators'] == 1) ? ' mod)' : ' mods)') + '</i>' + '</li>')
$('.actives').append(meeting_item);
// Set up join on click.
meeting_item.click(function(){
joinMeeting(m['name']);
});
}
var removeActiveMeeting = function(meeting){
if(meeting){
$('#' + meeting['name'].replace(' ', '_')).remove()
@ -137,20 +178,36 @@ $(document).on('turbolinks:load', function(){
if(data['method'] == 'create'){
// Create an empty meeting.
MEETINGS[data['meeting']] = {'name': data['meeting'],
'participants': 0,
'moderators': 0}
renderActiveMeeting(MEETINGS[data['meeting']])
'participants': [],
'moderators': []}
updateMeetingText(MEETINGS[data['meeting']])
updatePreviousMeetings();
if (WAITING.hasOwnProperty(data['meeting'])){ delete WAITING[data['meeting']]; }
} else if(data['method'] == 'destroy'){
removeActiveMeeting(MEETINGS[data['meeting']])
PreviousMeetings.uniqueAdd([data['meeting']])
delete MEETINGS[data['meeting']]
} else if(data['method'] == 'join'){
handleUser(data, 1)
updateMeetingText(MEETINGS[data['meeting']])
addUser(data)
} else if(data['method'] == 'leave'){
handleUser(data, -1)
removeUser(data)
} else if(data['method'] == 'waiting'){
// Handle waiting meeting.
if(WAITING.hasOwnProperty(data['meeting'])){
WAITING[data['meeting']]['users'].push(data['user'])
updateMeetingText(WAITING[data['meeting']])
} else {
WAITING[data['meeting']] = {'name': data['meeting'],
'users': [data['user']]}
updateMeetingText(WAITING[data['meeting']])
}
} else if((data['method'] == 'no_longer_waiting') && (WAITING.hasOwnProperty(data['meeting']))){
WAITING[data['meeting']]['users'].splice(WAITING[data['meeting']]['users'].indexOf(data['user']), 1)
updateMeetingText(WAITING[data['meeting']])
if(WAITING[data['meeting']]['users'].length == 0){
removeActiveMeeting(WAITING[data['meeting']])
delete WAITING[data['meeting']]
}
}
}
}

View File

@ -17,7 +17,8 @@
(function() {
var waitForModerator = function(url) {
$.get(url + "/wait", function(html) {
localStorage.setItem("waitingName", $('.meeting-user-name').val());
$.post(url + "/wait", {name: $('.meeting-user-name').val()}, function(html) {
$(".center-panel-wrapper").html(html);
});
if (!Meeting.getInstance().getWaitingForMod()) {

View File

@ -287,14 +287,17 @@ class BbbController < ApplicationController
actioncable_event('create', params['id'], params['room_id'])
elsif eventName == "meeting_destroyed_event"
actioncable_event('destroy', params['id'], params['room_id'])
record_id = event['payload']['meeting_id'].split('-')[0]
rec_info = bbb_get_recordings({recordID: record_id})
rec_info = rec_info[:recordings].first
# Remove the webhook.
webhook_remove(rec_info[:metadata][:"gl-webhooks-callback-url"])
elsif eventName == "user_joined_message"
actioncable_event('join', params['id'], params['room_id'], event['payload']['user']['role'])
actioncable_event('join', params['id'], params['room_id'], event['payload']['user']['name'], event['payload']['user']['role'])
elsif eventName == "user_left_message"
actioncable_event('leave', params['id'], params['room_id'], event['payload']['user']['role'])
actioncable_event('leave', params['id'], params['room_id'], event['payload']['user']['name'], event['payload']['user']['role'])
else
logger.info "Callback event will not be treated. Event name: #{eventName}"
end
@ -302,11 +305,12 @@ class BbbController < ApplicationController
render head(:ok) && return
end
def actioncable_event(method, id, room_id, role = 'none')
def actioncable_event(method, id, room_id, user = 'none', role = 'none')
ActionCable.server.broadcast 'refresh_meetings',
method: method,
meeting: id,
room: room_id,
user: user,
role: role
end

View File

@ -34,14 +34,29 @@ class LandingController < ApplicationController
end
end
def send_data
render json: bbb.get_meetings
def send_meetings_data
render json: {active: bbb.get_meetings, waiting: WaitingList.waiting}
end
def wait_for_moderator
WaitingList.add(params[:room_id], params[:name], params[:id])
ActionCable.server.broadcast 'refresh_meetings',
method: 'waiting',
meeting: params[:id],
room: params[:room_id],
user: params[:name]
render layout: false
end
def no_longer_waiting
WaitingList.remove(params[:room_id], params[:name], params[:id])
ActionCable.server.broadcast 'refresh_meetings',
method: 'no_longer_waiting',
meeting: params[:id],
room: params[:room_id],
user: params[:name]
end
def session_status_refresh
@user = User.find_by(encrypted_id: params[:room_id])
if @user.nil?

View File

@ -100,6 +100,8 @@ module BbbApi
rescue BigBlueButton::BigBlueButtonException => exc
logger.info "BBB error on create #{exc.key}: #{exc.message}"
end
WaitingList.empty(options[:room_owner], options[:meeting_name])
# And then get meeting info
bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil )

View File

@ -28,3 +28,11 @@
<%= image_tag "loading-indicator.gif", :alt => "" %>
</div>
<% end %>
<script>
// Notify the server when a user is no longer waiting to join a meeting.
window.addEventListener("beforeunload", function(e){
$.post(window.location.href + '/no_longer_wait', {name: localStorage.waitingName});
}, false);
</script>

View File

@ -0,0 +1,35 @@
# Stores data on waiting users on the server side so
# we can pass it to clients when they reload the page.
class WaitingList
@waiting = {}
def self.waiting
@waiting
end
def self.add(room, user, meeting)
@waiting[room] = {} unless @waiting.has_key?(room)
@waiting[room][meeting] = [] unless @waiting[room].has_key?(meeting)
@waiting[room][meeting] << user
end
def self.remove(room, user, meeting)
if @waiting.has_key?(room) then
if @waiting[room].has_key?(meeting) then
@waiting[room][meeting].slice!(@waiting[room][meeting].index(user))
@waiting[room].delete(meeting) if @waiting[room][meeting].length == 0
@waiting.delete(room) if @waiting[room].length == 0
end
end
end
def self.empty(room, meeting)
if @waiting.has_key?(room) then
if @waiting[room].has_key?(meeting) then
@waiting[room].delete(meeting)
@waiting.delete(room) if @waiting[room].length == 0
end
end
end
end

View File

@ -44,13 +44,14 @@ Rails.application.routes.draw do
delete '/(:id)/recordings/:record_id', to: 'bbb#delete_recordings', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash}
delete '/:id/end', to: 'bbb#end', defaults: {format: 'json'}, :constraints => {:id => disallow_slash}
get '/:id/wait', to: 'landing#wait_for_moderator', :constraints => {:id => disallow_slash}
post '/:id/wait', to: 'landing#wait_for_moderator', :constraints => {:id => disallow_slash}
post '/:id/no_longer_wait', to: 'landing#no_longer_waiting', :constraints => {:id => disallow_slash}
get '/:id/session_status_refresh', to: 'landing#session_status_refresh', :constraints => {:id => disallow_slash}
end
post '/:room_id/:id/callback', to: 'bbb#callback', :constraints => {:id => disallow_slash, :room_id => disallow_slash}
# routes shared between meetings and rooms
get '/(:room_id)/request', to: 'landing#send_data', :defaults => { :format => 'xml' }
get '/(:room_id)/request', to: 'landing#send_meetings_data', :defaults => { :format => 'xml' }
get '/(:room_id)/:id/join', to: 'bbb#join', defaults: {room_id: nil, format: 'json'}, :constraints => {:id => disallow_slash, :room_id => disallow_slash}
get '/(:room_id)/:id', to: 'landing#resource', as: :meeting_room, defaults: {room_id: nil}, :constraints => {:id => disallow_slash, :room_id => disallow_slash}
end