/* 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(); // Date pickers $('.date-picker').datepicker({ autoclose: true, format: 'yyyy-mm-dd', todayHighlight: true, todayBtn: 'linked', weekStart: 1, endDate: '1', language: currentLang }); // Regex to check a date var dateRe = /^\d{4}\-\d{1,2}\-\d{1,2}$/; // 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'); // 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]; } return string; } // POST a JSON req on the backend API function vroomApi(action, param, success, error){ if (typeof error !== 'function'){ error = function(data){ data = data.responseJSON; if (data.msg){ $.notify(data.msg, 'error'); } else{ $.notify(localize('ERROR_OCCURRED'), 'error'); } }; } if (typeof success !== 'function'){ success = function(){}; } $.ajax({ data: { req: JSON.stringify({ action: action, param: param }) }, error: function(data){ error(data); }, success: function(data){ success(data); } }); } // Handle lang switch drop down menu $('#switch_lang').change(function(){ vroomApi( 'switch_lang', { language: $('#switch_lang').val() }, function(data){ window.location.reload(); } ); }); // Escape entities to prevent XSS function stringEscape(string){ return $('
').text(string).html(); } // 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 in a string // 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, type, content){ var blob = new Blob([content], {type: type}); saveAs(blob, filename); } // Return current time formatted as hh:mm:ss 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 overloading the backend // if someone start clicking quickly function suspendButton(el){ $(el).prop('disabled', true); setTimeout(function(){ $(el).prop('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 // Depending on their corresponding switch $('#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 join password if ($('#joinPassSet').bootstrapSwitch('state')){ // Both must match if ($('#joinPass').val() !== $('#joinPassConfirm').val()){ $('#joinPassConfirm').notify(localize('PASSWORDS_DO_NOT_MATCH'), 'error'); return false; } // Empty password are not valid. We can't use the switch state, cause when editing a room // with a password already set, those field are empty if ($('#joinPassFields').css('display') !== 'none' && $('#joinPass').val() === ''){ $('#joinPass').notify(localize('PASSWORDS_CANNOT_BE_EMPTY'), 'error'); return false; } } // Same for the owner password if ($('#ownerPassSet').bootstrapSwitch('state')){ if ($('#ownerPass').val() !== $('#ownerPassConfirm').val()){ $('#ownerPassConfirm').notify(localize('PASSWORDS_DO_NOT_MATCH'), 'error'); return false; } if ($('#ownerPassFields').css('display') !== 'none' && $('#ownerPass').val() === ''){ $('#ownerPass').notify(localize('PASSWORDS_CANNOT_BE_EMPTY'), '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'); // TODO: find how to display correctly the 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(){ if ($(this).val() !== ''){ emails.push($(this).val()); } }); vroomApi( 'update_room_conf', { room: roomName, locked: locked, ask_for_name: askForName, join_password: joinPass, owner_password: ownerPass, persistent: persist, max_members: members, emails: emails }, 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){ vroomApi( 'get_room_conf', { room: roomName, }, 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-notification').find('.email-entry:first').remove(); } else{ $('#email-list-notification').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); $('#joinPassFields,#ownerPassFields').hide(); // exec a callback if needed if (typeof cb === 'function'){ cb(); } } ); } // Used on the index page function initIndex(){ var room; // Submit the home 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{ vroomApi( 'create_room', { room: $('#roomName').val() }, function(data) { // On success, the room has been created, lets join it room = data.room; window.location.assign(rootUrl + data.room); }, 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 feedback page function initFeedback(){ $('#email').on('input', function(){ if (!$('#email').val().match(/\S+@\S+/) && $('#email').val() !== ''){ $('#email').parent().addClass('has-error'); } else{ $('#email').parent().removeClass('has-error'); } }); $('#comment').on('input', function(){ if ($('#comment').val() === ''){ $('#comment').parent().addClass('has-error'); } else{ $('#comment').parent().removeClass('has-error'); } }); $('#feedback-form').submit(function(){ var ok = true; if ($('#email').parent().hasClass('has-error')){ ok = false; $('#email').notify(localize('ERROR_MAIL_INVALID'), 'error'); } if ($('#comment').parent().hasClass('has-error') || $('#comment').val() === ''){ ok = false; $('#comment').notify(localize('ERROR_COMMENT_INVALID'), 'error').parent().addClass('has-error'); } return ok; }); } // 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; var itemPerPage = $('#item-per-page').val(); // 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($('