forked from External/greenlight
431 lines
14 KiB
CoffeeScript
431 lines
14 KiB
CoffeeScript
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
|
|
#
|
|
# Copyright (c) 2016 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/>.
|
|
|
|
# Recordings class
|
|
|
|
_recordingsInstance = null
|
|
|
|
class @Recordings
|
|
# adding or removing a column will require updates to all subsequent column positions
|
|
COLUMNS = [
|
|
'DATE',
|
|
'NAME',
|
|
'PREVIEW',
|
|
'DURATION',
|
|
'PARTICIPANTS',
|
|
'PLAYBACK',
|
|
'VISIBILITY',
|
|
'LISTED',
|
|
'ACTION'
|
|
]
|
|
COLUMN = {}
|
|
i = 0
|
|
for c in COLUMNS
|
|
COLUMN[c] = i++
|
|
|
|
constructor: ->
|
|
recordingsObject = this
|
|
|
|
# configure the datatable for recordings
|
|
this.table = $('#recordings').dataTable({
|
|
data: [],
|
|
rowId: 'id',
|
|
paging: false,
|
|
dom: 't',
|
|
info: false,
|
|
order: [[ COLUMN.DATE, "desc" ]],
|
|
language: {
|
|
emptyTable: '<h3>'+I18n.no_recordings_yet+'</h3>',
|
|
zeroRecords: '<h3>'+I18n.no_recordings+'</h3>'
|
|
},
|
|
columns: [
|
|
{ data: "start_time" },
|
|
{ data: "name", visible: $(".page-wrapper.rooms").data('main-room') },
|
|
{ data: "previews", orderable: false },
|
|
{ data: "duration" },
|
|
{ data: "participants" },
|
|
{ data: "playbacks", orderable: false },
|
|
{ data: "published" },
|
|
{ data: "listed", visible: false },
|
|
{ data: "id", orderable: false }
|
|
],
|
|
columnDefs: [
|
|
{
|
|
targets: COLUMN.DATE,
|
|
render: (data, type, row) ->
|
|
if type == 'display'
|
|
date = new Date(data)
|
|
title = date
|
|
.toLocaleString($('html').attr('lang'),
|
|
{month: 'long', day: 'numeric', year: 'numeric',
|
|
hour12: 'true', hour: '2-digit', minute: '2-digit'})
|
|
timeago = '<time datetime="'+date.toISOString()+'" data-time-ago="'+date.toISOString()+'">'+date.toISOString()+'</time>'
|
|
return title+'<span class="timeago">('+timeago+')</span>'
|
|
return data
|
|
},
|
|
{
|
|
targets: COLUMN.PREVIEW,
|
|
render: (data, type, row) ->
|
|
if type == 'display'
|
|
str = ''
|
|
if row.published
|
|
for d in data
|
|
str += '<img height="50" width="50" class="img-thumbnail" src="'+d.url+'" alt="'+d.alt+'"></img><img class="img-thumbnail large" src="'+d.url+'"></img>'
|
|
return str
|
|
return data
|
|
},
|
|
{
|
|
targets: COLUMN.PLAYBACK,
|
|
render: (data, type, row) ->
|
|
if type == 'display'
|
|
str = ''
|
|
if row.published
|
|
if data.length == 1
|
|
str = '<a class="btn btn-default play-tooltip" href="'+data[0].url+'" target="_blank"><i class="fa fa-play-circle"></i></a>'
|
|
else
|
|
for d in data
|
|
str += '<a href="'+d.url+'" target="_blank">'+d.type_i18n+'</a> '
|
|
return str
|
|
return data
|
|
},
|
|
{
|
|
targets: COLUMN.VISIBILITY,
|
|
render: (data, type, row) ->
|
|
visibility = ['unpublished', 'unlisted', 'published']
|
|
if row.published
|
|
if row.listed
|
|
state = visibility[2]
|
|
else
|
|
state = visibility[1]
|
|
else
|
|
state = visibility[0]
|
|
if type == 'display'
|
|
return I18n[state]
|
|
return state
|
|
},
|
|
{
|
|
targets: COLUMN.ACTION,
|
|
render: (data, type, row) ->
|
|
if type == 'display'
|
|
roomName = Meeting.getInstance().getMeetingId()
|
|
recordingActions = $('.hidden-elements').find('.recording-actions')
|
|
classes = ['recording-unpublished', 'recording-unlisted', 'recording-published']
|
|
if row.published
|
|
if row.listed
|
|
cls = classes[2]
|
|
else
|
|
cls = classes[1]
|
|
else
|
|
cls = classes[0]
|
|
trigger = recordingActions.find('.recording-update-trigger')
|
|
trigger.removeClass(classes.join(' '))
|
|
trigger.addClass(cls)
|
|
|
|
return recordingActions.html()
|
|
return data
|
|
}
|
|
]
|
|
})
|
|
options = {
|
|
selector: '.delete-tooltip',
|
|
placement: 'bottom',
|
|
title: I18n.delete_recording
|
|
};
|
|
$('#recordings').tooltip(options)
|
|
|
|
options.selector = '.visibility-tooltip'
|
|
options.title = I18n.change_visibility
|
|
$('#recordings').tooltip(options)
|
|
|
|
options.selector = '.play-tooltip'
|
|
options.title = I18n.play_recording
|
|
$('#recordings').tooltip(options)
|
|
|
|
options.selector = '.youtube-tooltip'
|
|
options.title = I18n.upload_youtube
|
|
$('#recordings').tooltip(options)
|
|
|
|
options.selector = '.upload-tooltip'
|
|
options.title = I18n.share
|
|
$('#recordings').tooltip(options)
|
|
|
|
options.selector = '.mail-tooltip'
|
|
options.title = I18n.mail_recording
|
|
$('#recordings').tooltip(options)
|
|
|
|
$(document).one "turbolinks:before-cache", =>
|
|
@getTable().api().clear().draw().destroy()
|
|
|
|
# enable popovers
|
|
# can't use trigger:'focus' because it doesn't work will with buttons inside
|
|
# the popover
|
|
options = {
|
|
selector: '.has-popover',
|
|
html: true,
|
|
trigger: 'click',
|
|
title: ->
|
|
return $(this).data("popover-title");
|
|
content: ->
|
|
bodySelector = $(this).data("popover-body")
|
|
return $(bodySelector).html()
|
|
}
|
|
$('#recordings').popover(options)
|
|
|
|
# close popovers manually when clicking outside of them or in buttons
|
|
# with [data-dismiss="popover"]
|
|
# careful to hide only the open popover and not all of them, otherwise they won't reopen
|
|
$('body').on 'click', (e) ->
|
|
$('.has-popover').each ->
|
|
if !$(this).is(e.target) and $(this).has(e.target).length == 0 and $('.popover.in').has(e.target).length == 0
|
|
if $(this).next(".popover.in").length > 0
|
|
$(this).popover('hide')
|
|
$(document).on 'click', '[data-dismiss="popover"]', (e) ->
|
|
$('.has-popover').each ->
|
|
if $(this).next(".popover.in").length > 0
|
|
$(this).popover('hide')
|
|
|
|
# Gets the current instance or creates a new one
|
|
@getInstance: ->
|
|
if _recordingsInstance && Recordings.initialized()
|
|
return _recordingsInstance
|
|
_recordingsInstance = new Recordings()
|
|
return _recordingsInstance
|
|
|
|
@initialize: ->
|
|
Recordings.getInstance()
|
|
|
|
@initialized: ->
|
|
return $.fn.DataTable.isDataTable('#recordings') && _recordingsInstance
|
|
|
|
draw: ->
|
|
if !@isOwner()
|
|
@table.api().columns(COLUMN.LISTED).search('true')
|
|
@table.api().columns.adjust().draw()
|
|
|
|
# refresh the recordings from the server
|
|
refresh: ->
|
|
table_api = this.table.api()
|
|
$.get @getRecordingsURL(), (data) =>
|
|
@setOwner(data.is_owner)
|
|
if !@owner
|
|
table_api.column(COLUMN.ACTION).visible(false)
|
|
table_api.column(COLUMN.VISIBILITY).visible(false)
|
|
for recording in data.recordings
|
|
# NOTE: Temporary fix for the minutes to milliseconds bug some recordings may have
|
|
# experienced when transitioning from BigBlueButton 1.1 to BigBlueButton 2.0.
|
|
recording.duration = if recording.duration < 1000 then recording.duration else parseInt(recording.length / 60000)
|
|
data.recordings.sort (a,b) ->
|
|
return new Date(b.start_time) - new Date(a.start_time)
|
|
table_api.clear()
|
|
table_api.rows.add(data.recordings)
|
|
@draw()
|
|
|
|
if $(".page-wrapper.rooms").data('main-room')
|
|
recording_names = data.recordings.map (r) ->
|
|
return r.name
|
|
output = {}
|
|
for key in [0...recording_names.length]
|
|
output[recording_names[key]] = recording_names[key]
|
|
PreviousMeetings.uniqueAdd(value for key, value of output)
|
|
|
|
|
|
# setup click handlers for the action buttons
|
|
setupActionHandlers: ->
|
|
table_api = this.table.api()
|
|
recordingsObject = this
|
|
selectedUpload = null
|
|
canUpload = false
|
|
|
|
@getTable().on 'click', '.recording-update', (event) ->
|
|
btn = $(this)
|
|
row = table_api.row($(this).closest('tr')).data()
|
|
url = recordingsObject.getRecordingsURL()
|
|
id = row.id
|
|
|
|
published = btn.data('visibility') == "unlisted" ||
|
|
btn.data('visibility') == "published"
|
|
listed = btn.data('visibility') == "published"
|
|
|
|
btn.prop('disabled', true)
|
|
|
|
data = { published: published.toString() }
|
|
data["meta_" + GreenLight.META_LISTED] = listed.toString();
|
|
$.ajax({
|
|
method: 'PATCH',
|
|
url: url+'/'+id,
|
|
data: data
|
|
}).done((data) ->
|
|
btn.prop('disabled', false)
|
|
).fail((xhr, text) ->
|
|
btn.prop('disabled', false)
|
|
)
|
|
|
|
@getTable().on 'click', '.recording-delete', (event) ->
|
|
btn = $(this)
|
|
row = table_api.row($(this).closest('tr'))
|
|
url = recordingsObject.getRecordingsURL()
|
|
id = row.data().id
|
|
btn.prop('disabled', true)
|
|
$.ajax({
|
|
method: 'DELETE',
|
|
url: url+'/'+id
|
|
}).done((data) ->
|
|
btn.prop('disabled', false)
|
|
).fail((xhr, text) ->
|
|
btn.prop('disabled', false)
|
|
if xhr.status == 404
|
|
row.remove();
|
|
recordingsObject.draw()
|
|
showAlert(I18n.recording_deleted, 4000);
|
|
)
|
|
|
|
@getTable().on 'click', '.upload-button', (event) ->
|
|
btn = $(this)
|
|
row = table_api.row($(this).closest('tr')).data()
|
|
url = recordingsObject.getRecordingsURL()
|
|
id = row.id
|
|
|
|
title = $('#video-title').val()
|
|
privacy_status = $('input[name=privacy_status]:checked').val()
|
|
|
|
if title == ''
|
|
title = row.name
|
|
|
|
$.ajax({
|
|
method: 'POST',
|
|
url: url+'/'+id
|
|
data: {video_title: title, privacy_status: privacy_status}
|
|
success: (data) ->
|
|
|
|
if data['url'] != null
|
|
window.location.href = data['url']
|
|
else
|
|
cloud = selectedUpload.find('.cloud-blue')
|
|
check = selectedUpload.find('.green-check')
|
|
spinner = selectedUpload.find('.load-spinner')
|
|
|
|
showAlert(I18n.successful_upload, 4000);
|
|
|
|
spinner.hide()
|
|
check.show()
|
|
|
|
setTimeout ( ->
|
|
cloud.show()
|
|
check.hide()
|
|
), 2500
|
|
})
|
|
|
|
selectedUpload.find('.cloud-blue').hide()
|
|
selectedUpload.find('.load-spinner').show()
|
|
|
|
@getTable().on 'click', '.mail-recording', (event) ->
|
|
btn = $(this)
|
|
row = table_api.row($(this).closest('tr')).data()
|
|
url = recordingsObject.getRecordingsURL()
|
|
id = row.id
|
|
|
|
# Take the username from the header.
|
|
username = $('#title-header').text().replace('Welcome ', '').trim()
|
|
|
|
recording_url = row.playbacks[0].url
|
|
webcams_url = getHostName(recording_url) + '/presentation/' + id + '/video/webcams.webm'
|
|
subject = username + I18n.recording_mail_subject
|
|
body = I18n.recording_mail_body + "\n\n" + recording_url + "\n\n" + I18n.email_footer_1 + "\n" + I18n.email_footer_2
|
|
|
|
mailto = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);
|
|
window.open(mailto);
|
|
|
|
@getTable().on 'click', '.youtube-upload', (event) ->
|
|
row = table_api.row($(this).closest('tr')).data()
|
|
$('#video-title').attr('value', row.name)
|
|
|
|
@getTable().on 'click', '.cloud-upload', (event) ->
|
|
btn = $(this)
|
|
row = table_api.row($(this).closest('tr')).data()
|
|
id = row.id
|
|
|
|
selectedUpload = $(this)
|
|
|
|
# Determine if the recording can be uploaded to Youtube.
|
|
$.ajax({
|
|
method: 'POST',
|
|
data: {'rec_id': id},
|
|
async: false,
|
|
url: recordingsObject.getRecordingsURL() + '/can_upload'
|
|
}).success((res_data) ->
|
|
canUpload = res_data['uploadable']
|
|
)
|
|
|
|
youtube_button = $('.share-popover').find('.youtube-upload')
|
|
|
|
attr = $(this).attr('data-popover-body');
|
|
|
|
# Check if the cloud button has a popover body.
|
|
if (typeof attr == typeof undefined || attr == false)
|
|
switch canUpload
|
|
# We can upload the recording.
|
|
when 'true'
|
|
youtube_button.attr('title', I18n.upload_youtube)
|
|
youtube_button.removeClass('disabled-button')
|
|
youtube_button.addClass('has-popover')
|
|
youtube_button.show()
|
|
# Can't upload because uploading is disabled.
|
|
when 'uploading_disabled'
|
|
youtube_button.hide()
|
|
# Can't upload because account is not authenticated with Google.
|
|
when 'invalid_provider'
|
|
youtube_button.attr('title', I18n.invalid_provider)
|
|
youtube_button.addClass('disabled-button')
|
|
youtube_button.removeClass('has-popover')
|
|
youtube_button.show()
|
|
# Can't upload because recording does not contain video.
|
|
else
|
|
youtube_button.attr('title', I18n.no_video)
|
|
youtube_button.addClass('disabled-button')
|
|
youtube_button.removeClass('has-popover')
|
|
youtube_button.show()
|
|
|
|
$(this).attr('data-popover-body', '.share-popover')
|
|
$(this).popover('show')
|
|
else
|
|
$(this).popover('hide')
|
|
$(this).removeAttr('data-popover-body')
|
|
|
|
|
|
@getTable().on 'draw.dt', (event) ->
|
|
$('time[data-time-ago]').timeago();
|
|
|
|
getTable: ->
|
|
@table
|
|
|
|
getHostName = (url) ->
|
|
parser = document.createElement('a');
|
|
parser.href = url;
|
|
parser.origin;
|
|
|
|
getRecordingsURL: ->
|
|
if $(".page-wrapper.rooms").data('main-room')
|
|
base_url = Meeting.buildRootURL()+'/'+$('body').data('resource')+'/'+Meeting.getInstance().getAdminId()
|
|
else
|
|
base_url = $('.meeting-url').val()
|
|
base_url+'/recordings'
|
|
|
|
isOwner: ->
|
|
@owner
|
|
|
|
setOwner: (owner) ->
|
|
@owner = owner
|