/* This file is part of the VROOM project released under the MIT licence Copyright 2014-2015 Daniel Berteaud */ // Default notifications $.notify.defaults({ globalPosition: 'bottom left' }); // Enable tooltip on required elements $('.help').tooltip({ container: 'body', trigger: 'hover' }); $('.popup').popover({ container: 'body', trigger: 'focus' }); $('.modal').on('show.bs.modal', function(){ $('.help').tooltip('hide'); }); // Enable bootstrap-swicth $('.bs-switch').bootstrapSwitch(); // Strings we need translated var locale = {}, def_locale = {}; // When pagination is done, how many item per page var itemPerPage = 20; // Some global vars, like // the SimpleWebRTC object var webrtc = undefined; // The current room configuration var roomInfo = {}; // The current peers (we init the list with only ourself) var peers = { local: { screenShared: false, micMuted: false, videoPaused: false, displayName: '', color: chooseColor(), role: 'participant', hasVideo: true } }; // Mark current page link as active $('#lnk_' + page).addClass('active'); // Localize the strings we need $.getJSON(rootUrl + 'localize/' + currentLang, function(data){ locale = data; }); // If current locale isn't EN, retrieve EN locale as a fallback if (currentLang !== 'en'){ $.getJSON(rootUrl + 'localize/en' , function(data){ def_locale = data; }); } // Default ajax setup $.ajaxSetup({ url: rootUrl + 'api', type: 'POST', dataType: 'json', headers: { 'X-VROOM-API-Key': api_key } }); // // Define a few functions // // Localize a string, or just print it if localization doesn't exist function localize(string){ if (locale[string]){ return locale[string]; } else if (def_locale[string]){ return def_locale[string]; } return string; } // Parse and display an error when an API call failed function showApiError(data){ data = data.responseJSON; if (data.msg){ $.notify(data.msg, 'error'); } else{ $.notify(localize('ERROR_OCCURRED'), 'error'); } } // Handle lang switch drop down menu $('#switch_lang').change(function(){ $.ajax({ data: { req: JSON.stringify({ action: 'switch_lang', param : { language: $('#switch_lang').val() } }) }, error: function(data){ showApiError(data); }, success: function(data){ window.location.reload(); } }); }); // Escape entities to prevent XSS function stringEscape(string){ string = string.replace(/[\u00A0-\u99999<>\&]/gim, function(i) { return '&#' + i.charCodeAt(0) + ';'; }); return string; } // Select a color (randomly) from this list, used for text chat, and the name under the preview function chooseColor(){ // Shamelessly taken from http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ var colors = [ '#B1C7FD', '#DDFDB1', '#FDB1F3', '#B1FDF0', '#FDDAB1', '#C4B1FD', '#B4FDB1', '#FDB1CA', '#B1E1FD', '#F7FDB1', '#EDB1FD', '#B1FDD7', '#FDC1B1', '#B1B7FD', '#CEFDB1', '#FDB1E4', '#B1FAFD', '#FDEAB1', '#D4B1FD', '#B1FDBD', '#FDB1BB', '#B1D1FD', '#E7FDB1', '#FDB1FD', '#B1FDE7', '#B1FDE7' ]; return colors[Math.floor(Math.random() * colors.length)]; } // Just play a sound function playSound(sound){ var audio = new Audio(rootUrl + 'snd/' + sound); audio.play(); } // Request full screen function fullScreen(el){ if (el.requestFullScreen){ el.requestFullScreen(); } else if (el.webkitRequestFullScreen){ el.webkitRequestFullScreen(); } else if (el.mozRequestFullScreen){ el.mozRequestFullScreen(); } } // Linkify urls // Taken from http://rickyrosario.com/blog/converting-a-url-into-a-link-in-javascript-linkify-function/ function linkify(text){ if (text) { text = text.replace( /((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi, function(url){ var full_url = url; if (!full_url.match('^https?:\/\/')) { full_url = 'http://' + full_url; } return '' + url + ''; } ); } return text; } // Save content to a file function downloadContent(filename, content){ var blob = new Blob([content], {type: 'text/html;charset=utf-8'}); saveAs(blob, filename); } // Return current time formatted as XX:XX:XX function getTime(){ var d = new Date(); var hours = d.getHours().toString(), minutes = d.getMinutes().toString(), seconds = d.getSeconds().toString(); hours = (hours.length < 2) ? '0' + hours : hours; minutes = (minutes.length < 2) ? '0' + minutes : minutes; seconds = (seconds.length < 2) ? '0' + seconds : seconds; return hours + ':' + minutes + ':' + seconds; } // Convert dates from UTC to local TZ function utc2Local(date) { var newDate = new Date(date.getTime() + date.getTimezoneOffset() * 60 * 1000); var offset = date.getTimezoneOffset() / 60; var hours = date.getHours(); newDate.setHours(hours - offset); return newDate; } // Temporarily suspend a button, prevent abuse function suspendButton(el){ $(el).attr('disabled', true); setTimeout(function(){ $(el).attr('disabled', false); }, 1000); } // get max height for the main video and the preview div function maxHeight(){ // Which is the window height, minus toolbar, and a margin of 25px return $(window).height()-$('#toolbar').height()-25; } // Create a new email input field function addEmailInputField(form, val){ var parentEl = $('#' + form), currentEntry = parentEl.find('.email-entry:last'), newEntry = $(currentEntry.clone()).appendTo(parentEl); newEntry.find('input').val(val); newEntry.removeClass('has-error'); adjustAddRemoveEmailButtons(form); } // Adjust add and remove buttons foir email notifications function adjustAddRemoveEmailButtons(form){ $('#' + form).find('.email-entry:not(:last) .btn-add-email') .removeClass('btn-primary').removeClass('btn-add-email') .addClass('btn-danger').addClass('btn-remove-email') .html(''); $('#' + form).find('.email-entry:last .btn-remove-email') .removeClass('btn-danger').removeClass('btn-remove-email') .addClass('btn-primary').addClass('btn-add-email') .html(''); } // Add emails input field $(document).on('click','button.btn-add-email',function(e){ e.preventDefault(); addEmailInputField($(this).parents('.email-list:first').attr('id'), ''); }); // Remove email input field $(document).on('click','button.btn-remove-email',function(e){ e.preventDefault(); el = $(this).parents('.email-entry:first'); el.remove(); }); // Update the displayName of the peer // and its screen if any function updateDisplayName(id){ // We might receive the screen before the peer itself // so check if the object exists before using it, or fallback with empty values var display = (peers[id] && peers[id].hasName) ? stringEscape(peers[id].displayName) : ''; var color = (peers[id] && peers[id].color) ? peers[id].color : chooseColor(); var screenName = (peers[id] && peers[id].hasName) ? sprintf(localize('SCREEN_s'), stringEscape(peers[id].displayName)) : ''; $('#name_' + id).html(display).css('background-color', color); $('#name_' + id + '_screen').html(screenName).css('background-color', color); } // Handle owner/join password confirm $('#ownerPassConfirm').on('input', function() { if ($('#ownerPassConfirm').val() == $('#ownerPass').val() && $('#ownerPassConfirm').val() != ''){ $('#ownerPassConfirm').parent().removeClass('has-error'); } else{ $('#ownerPassConfirm').parent().addClass('has-error'); } }); $('#joinPassConfirm').on('input', function() { if ($('#joinPass').val() == $('#joinPassConfirm').val() && $('#joinPass').val() != ''){ $('#joinPassConfirm').parent().removeClass('has-error'); } else{ $('#joinPassConfirm').parent().addClass('has-error'); } }); // Hide or show password fields $('#joinPassSet').on('switchChange.bootstrapSwitch', function(event, state) { if (state){ $('#joinPassFields').show(200); } else{ $('#joinPassFields').hide(200); } }); $('#ownerPassSet').on('switchChange.bootstrapSwitch', function(event, state) { if (state){ $('#ownerPassFields').show(200); } else{ $('#ownerPassFields').hide(200); } }); // Submit the configuration form $('#configureRoomForm').submit(function(e){ e.preventDefault(); // check if passwords match if ($('#joinPassSet').bootstrapSwitch('state')){ if ($('#joinPass').val() !== $('#joinPassConfirm').val()){ $('#joinPassConfirm').notify(localize('PASSWORDS_DO_NOT_MATCH'), 'error'); return false; } } if ($('#ownerPassSet').bootstrapSwitch('state')){ if ($('#ownerPass').val() !== $('#ownerPassConfirm').val()){ $('#ownerPassConfirm').notify(localize('PASSWORDS_DO_NOT_MATCH'), 'error'); return false; } } var validEmail = true; $('.email-list').find('input').each(function(index, input){ if (!$(input).val().match(/\S+@\S+\.\S+/) && $(input).val() !== ''){ $(input).parent().addClass('has-error'); //$(input).parent().notify(localize('ERROR_MAIL_INVALID'), 'error'); validEmail = false; // Break the each loop and return an error now, we can't submit as is return false; } else{ $(input).parent().removeClass('has-error'); } }); if (!validEmail){ return false; } var locked = $('#lockedSet').bootstrapSwitch('state'), askForName = $('#askForNameSet').bootstrapSwitch('state'), joinPass = ($('#joinPassSet').bootstrapSwitch('state')) ? $('#joinPass').val() : false, ownerPass = ($('#ownerPassSet').bootstrapSwitch('state')) ? $('#ownerPass').val() : false, persist = ($('#persistentSet').length > 0) ? $('#persistentSet').bootstrapSwitch('state') : '', members = ($('#maxMembers').length > 0) ? $('#maxMembers').val() : 0, emails = []; $('input[name="emails[]"]').each(function(){ emails.push($(this).val()); }); $.ajax({ data: { req: JSON.stringify({ action: 'update_room_conf', param: { room: roomName, locked: locked, ask_for_name: askForName, join_password: joinPass, owner_password: ownerPass, persistent: persist, max_members: members, emails: emails } }) }, error: function(data){ showApiError(data); }, success: function(data){ // On success, reset the input fields, collapse the password inputs // and close the configuration modal $('#ownerPass,#ownerPassConfirm,#joinPass,#joinPassConfirm').val(''); $('#configureModal').modal('hide'); $('#joinPassFields,#ownerPassFields').hide(); $.notify(data.msg, 'info'); $('#configureRoomForm').trigger('room_conf_updated'); } }); }); // Get our role and other room settings from the server function getRoomInfo(cb){ $.ajax({ data: { req: JSON.stringify({ action: 'get_room_conf', param: { room: roomName, } }) }, error: function(data){ showApiError(data); }, success: function(data){ roomInfo = data; // Reset the list of email displayed, so first remove every input field but the last one // We keep it so we can clone it again $('.email-list').find('.email-entry:not(:last)').remove(); // Now add one input per email $.each(data.notif, function(index, obj){ addEmailInputField('email-list-notification', obj.email); }); // Now, remove the first one if the list is not empty if (Object.keys(data.notif).length > 0){ $('.email-list').find('.email-entry:first').remove(); } else{ $('.email-list').find('.email-entry:first').find('input:first').val(''); } adjustAddRemoveEmailButtons(); // Update config switches $('#lockedSet').bootstrapSwitch('state', data.locked); $('#askForNameSet').bootstrapSwitch('state', data.ask_for_name); $('#joinPassSet').bootstrapSwitch('state', data.join_auth); $('#ownerPassSet').bootstrapSwitch('state', data.owner_auth); // exec the callback if defined if (typeof cb === 'function'){ cb(); } } }); } // Used on the index page function initIndex(){ var room; // Submit the main form to create a room $('#createRoom').submit(function(e){ e.preventDefault(); // Do not submit if we know the name is invalid if (!$('#roomName').val().match(/^[\w\-]{0,49}$/)){ $('#roomName').parent().parent().notify(localize('ERROR_NAME_INVALID'), { class: 'error', position: 'bottom center' }); } else{ $.ajax({ data: { req: JSON.stringify({ action: 'create_room', param: { room: $('#roomName').val() } }) }, success: function(data) { room = data.room; window.location.assign(rootUrl + data.room); }, error: function(data){ data = data.responseJSON; if (data.err && data.err == 'ERROR_NAME_CONFLICT' ){ room = data.room; $('#conflictModal').modal('show'); } else if (data.msg){ $('#roomName').parent().parent().notify(data.msg, { class: 'error', position: 'bottom center' }); } else{ $.notify(localize('ERROR_OCCURRED'), 'error'); } } }); } }); // Handle join confirmation $('#confirmJoinButton').click(function(){ window.location.assign(rootUrl + room); }); // Handle cancel/choose another name $('#chooseAnotherNameButton').click(function(){ $('#roomName').val(''); $('#conflictModal').modal('hide'); }); // Check for invalid room name as user is typing $('#roomName').on('input', function(){ if (!$('#roomName').val().match(/^[\w\-]{0,49}$/)){ $('#roomName').parent().addClass('has-error'); } else{ $('#roomName').parent().removeClass('has-error'); } }); } // The documentation page function initDoc(){ window.onresize = function (){ $('#toc').width($('#toc').parents().width()); $('#toc').css('max-height', maxHeight() - 100 + 'px'); }; $('#toc').width($('#toc').parents().width()); $('#toc').css('max-height', maxHeight() - 100 + 'px'); $('#toc').toc({ elementClass: 'toc', ulClass: 'nav', heading: 'Table of content', indexingFormats: 'number' }); // Scroll to the table of content section when user scroll the mouse $('body').scrollspy({ target: '#toc', offset: $('#headerNav').outerHeight(true) + 40 }); // Small delay so the affix can start when everything is loaded setTimeout(function() { var $sideBar = $('#toc'); $sideBar.affix({ offset: { top: function() { var offsetTop = $sideBar.offset().top, sideBarMargin = parseInt($sideBar.children(0).css('margin-top'), 10), navOuterHeight = $('#headerNav').height(); return (this.top = offsetTop - navOuterHeight - sideBarMargin); }, bottom: function() { return (this.bottom = $('footer').outerHeight(true)); } } }); }, 200); } // Used on the room admin page function initAdminRooms(){ var roomList = {}; var matches = 0; // Update display of room list function updateRoomList(filter, min, max){ $('#roomList').html(''); var filterRe = new RegExp(filter, "gi"); var i = 0; matches = 0; $.each(roomList, function (index, obj){ if (filter === '' || obj.name.match(filterRe)){ matches++; if (i >= min && i < max){ var t = obj.create_date.split(/[- :]/); var create = utc2Local(new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5])).toLocaleString(); t = obj.last_activity.split(/[- :]/); var activity = utc2Local(new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5])).toLocaleString(); $('#roomList').append($('') .append($('').html(stringEscape(obj.name))) .append($('').html(stringEscape(create)).addClass('hidden-xs')) .append($('').html(stringEscape(activity)).addClass('hidden-xs')) .append($('').html(obj.members).addClass('hidden-xs hidden-sm')) .append($('') .append($('
').addClass('btn-group') .append($('').addClass('btn btn-default btn-lg').attr('href',rootUrl + obj.name) .html( $('').addClass('glyphicon glyphicon-log-in') ) ) .append($('