Rename airtime_mvc/ to legacy/

This commit is contained in:
jo 2021-10-11 13:43:25 +02:00
parent f0879322c2
commit 3e18d42c8b
1316 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,5 @@
$(document).ready(function() {
$.ajaxSetup({
cache: false
});
});

View file

@ -0,0 +1,248 @@
var _playlist_jplayer;
var _idToPostionLookUp;
var URL_BAR_HEIGHT = 32;
/**
*When the page loads the ready function will get all the data it can from the hidden span elements
*and call one of three functions depending on weather the window was open to play an audio file,
*or a playlist or a show.
*/
$(document).ready(function(){
$.jPlayer.timeFormat.showHour = true;
var audioUri = $('.audioUri').text();
var audioMime = $('.audioMime').text();
var playlistID = $('.playlistID').text();
var playlistIndex = $('.playlistIndex').text();
var showID = $('.showID').text();
var showIndex = $('.showIndex').text();
var blockId = $('.blockId').text();
var blockIndex = $('.blockIndex').text();
_playlist_jplayer = new jPlayerPlaylist({
jPlayer: "#jquery_jplayer_1",
cssSelectorAncestor: "#jp_container_1"
},[], //array of songs will be filled with below's json call
{
swfPath: baseUrl+"js/jplayer",
supplied:"oga, mp3, m4v, m4a, wav",
size: {
width: "0px",
height: "0px",
cssClass: "jp-video-270p"
},
playlistOptions: {
autoPlay: false,
loopOnPrevious: false,
shuffleOnLoop: true,
enableRemoveControls: false,
displayTime: 0,
addTime: 0,
removeTime: 0,
shuffleTime: 0
},
ready: function(){
if (playlistID != "" && playlistID !== ""){
playAllPlaylist(playlistID, playlistIndex);
}else if (audioUri != "") {
playOne(audioUri, audioMime);
}else if (showID != "") {
playAllShow(showID, showIndex);
}else if(blockId != "" && blockIndex != ""){
playBlock(blockId, blockIndex);
}
}
});
$("#jp_container_1").on("mouseenter", "ul.jp-controls li", function(ev) {
$(this).addClass("ui-state-hover");
});
$("#jp_container_1").on("mouseleave", "ul.jp-controls li", function(ev) {
$(this).removeClass("ui-state-hover");
});
});
/**
* Sets up the jPlayerPlaylist to play.
* - Get the playlist info based on the playlistID give.
* - Update the playlistIndex to the position in the pllist to start playing.
* - Select the element played from and start playing. If playlist is null then start at index 0.
**/
function playAllPlaylist(p_playlistID, p_playlistIndex) {
var viewsPlaylistID = $('.playlistID').text();
if ( _idToPostionLookUp !== undefined && viewsPlaylistID == p_playlistID ) {
play(p_playlistIndex);
}else {
buildplaylist(baseUrl+"audiopreview/get-playlist/playlistID/"+p_playlistID, p_playlistIndex);
}
}
function playBlock(p_blockId, p_blockIndex)
{
var viewsBlockId = $('.blockId').text();
if ( _idToPostionLookUp !== undefined && viewsBlockId == p_blockId ) {
play(p_blockIndex);
} else {
buildplaylist(baseUrl+"audiopreview/get-block/blockId/"+p_blockId, p_blockIndex);
}
}
/**
* Sets up the show to play.
* checks with the show id given to the show id on the page/view
* if the show id and the page or views show id are the same it means the user clicked another
* file in the same show, so don't refresh the show content tust play the track from the preloaded show.
* if the the ids are different they we'll need to get the show's context so create the uri
* and call the controller.
**/
function playAllShow(p_showID, p_index) {
var viewsShowID = $('.showID').text();
if ( _idToPostionLookUp !== undefined && viewsShowID == p_showID ) {
play(p_index);
}else {
buildplaylist(baseUrl+"audiopreview/get-show/showID/"+p_showID, p_index);
}
}
/**
* This function will call the AudiopreviewController to get the contents of
* either a show or playlist Looping throught the returned contents and
* creating media for each track.
*
* Then trigger the jplayer to play the list.
*/
function buildplaylist(p_url, p_playIndex) {
_idToPostionLookUp = Array();
$.getJSON(p_url, function(data){ // get the JSON array produced by my PHP
var myPlaylist = new Array();
var media;
var index;
var total = 0;
var skipped = 0;
for(index in data) {
if (data[index]['type'] == 0) {
if (data[index]['element_mp3'] != undefined){
media = {title: data[index]['element_title'],
artist: data[index]['element_artist'],
mp3:data[index]['uri']
};
} else if (data[index]['element_oga'] != undefined) {
media = {title: data[index]['element_title'],
artist: data[index]['element_artist'],
oga:data[index]['uri']
};
} else if (data[index]['element_m4a'] != undefined) {
media = {title: data[index]['element_title'],
artist: data[index]['element_artist'],
m4a:data[index]['uri']
};
} else if (data[index]['element_wav'] != undefined) {
media = {title: data[index]['element_title'],
artist: data[index]['element_artist'],
wav:data[index]['uri']
};
} else {
// skip this track since it's not supported
console.log("continue");
skipped++;
continue;
}
} else if (data[index]['type'] == 1) {
var mime = data[index]['mime'];
if (mime.search(/mp3/i) > 0 || mime.search(/mpeg/i) > 0) {
key = "mp3";
} else if (mime.search(/og(g|a)/i) > 0 || mime.search(/vorbis/i) > 0) {
key = "oga";
} else if (mime.search(/mp4/i) > 0) {
key = "m4a";
} else if (mime.search(/wav/i) > 0) {
key = "wav";
}
if (key) {
media = {title: data[index]['element_title'],
artist: data[index]['element_artist']
};
media[key] = data[index]['uri']
}
}
if (media && isAudioSupported(data[index]['mime'])) {
// javascript doesn't support associative array with numeric key
// so we need to remove the gap if we skip any of tracks due to
// browser incompatibility.
myPlaylist[index-skipped] = media;
}
// we should create a map according to the new position in the
// player itself total is the index on the player
_idToPostionLookUp[data[index]['element_id']] = total;
total++;
}
_playlist_jplayer.setPlaylist(myPlaylist);
_playlist_jplayer.option("autoPlay", true);
play(p_playIndex);
window.scrollbars = false;
var container = $("#jp_container_1");
// Add 2px to account for borders
window.resizeTo(container.width() + 2, container.height() + URL_BAR_HEIGHT + 2);
});
}
/**
*Function simply plays the given index, for playlists index can be different so need to look up the
*right index.
*/
function play(p_playlistIndex){
playlistIndex = _idToPostionLookUp[p_playlistIndex];
if(playlistIndex == undefined){
playlistIndex = 0
}
//_playlist_jplayer.select(playlistIndex);
_playlist_jplayer.play(playlistIndex);
}
/**
* Playing one audio track occurs from the library. This function will create the media, setup
* jplayer and play the track.
*/
function playOne(uri, mime) {
var playlist = new Array();
var media = null;
var key = null;
if (mime.search(/mp3/i) > 0 || mime.search(/mpeg/i) > 0) {
key = "mp3";
} else if (mime.search(/og(g|a)/i) > 0 || mime.search(/vorbis/i) > 0) {
key = "oga";
} else if (mime.search(/mp4/i) > 0) {
key = "m4a";
} else if (mime.search(/wav/i) > 0) {
key = "wav";
}
if (key) {
media = {title: $('.audioFileTitle').text() != 'null' ?$('.audioFileTitle').text():"",
artist: $('.audioFileArtist').text() != 'null' ?$('.audioFileArtist').text():""
};
media[key] = uri;
}
if (media) {
_playlist_jplayer.option("autoPlay", true);
playlist[0] = media;
_playlist_jplayer.setPlaylist(playlist);
_playlist_jplayer.play(0);
}
var container = $("#jp_container_1");
// Add 2px to account for borders
window.resizeTo(container.width() + 2, container.height() + URL_BAR_HEIGHT + 2);
}

View file

@ -0,0 +1,50 @@
var AIRTIME = (function(AIRTIME) {
var mod, DEFAULT_CLASS = 'ui-button ui-state-default', DISABLED_CLASS = 'ui-state-disabled';
if (AIRTIME.button === undefined) {
AIRTIME.button = {};
}
mod = AIRTIME.button;
mod.isDisabled = function(c, useParent) {
var button = $("." + c);
if (useParent) {
button = button.parent();
}
if (button.hasClass(DISABLED_CLASS)) {
return true;
}
return false;
};
mod.enableButton = function(c, useParent) {
if (useParent) {
var button = $("." + c).parent();
} else {
var button = $("." + c);
}
if (button.hasClass(DISABLED_CLASS)) {
button.removeClass(DISABLED_CLASS);
button.removeAttr('disabled');
}
};
mod.disableButton = function(c, useParent) {
if (useParent) {
var button = $("." + c).parent();
} else {
var button = $("." + c);
}
if (!button.hasClass(DISABLED_CLASS)) {
button.addClass(DISABLED_CLASS);
button.attr('disabled', 'disabled');
}
};
return AIRTIME;
}(AIRTIME || {}));

View file

@ -0,0 +1,24 @@
function isAudioSupported(mime){
var audio = new Audio();
if ((typeof mime) !== "string" || (mime === null)) {
return false;
}
var bMime = null;
if (mime.indexOf("ogg") != -1 || mime.indexOf("vorbis") != -1) {
bMime = 'audio/ogg; codecs="vorbis"';
} else {
bMime = mime;
}
//return a true of the browser can play this file natively, or if the
//file is an mp3 and flash is installed (jPlayer will fall back to flash to play mp3s).
//Note that checking the navigator.mimeTypes value does not work for IE7, but the alternative
//is adding a javascript library to do the work for you, which seems like overkill....
return (!!audio.canPlayType && audio.canPlayType(bMime) != "") ||
(mime.indexOf("mp3") != -1 && navigator.mimeTypes ["application/x-shockwave-flash"] != undefined) ||
(mime.indexOf("mp4") != -1 && navigator.mimeTypes ["application/x-shockwave-flash"] != undefined) ||
(mime.indexOf("mpeg") != -1 && navigator.mimeTypes ["application/x-shockwave-flash"] != undefined);
}

View file

@ -0,0 +1,358 @@
var previewWidth = 482,
previewHeight = 110;
$(document).ready(function() {
/* Removed as this is now (hopefully) unnecessary */
//$("#Panel").stickyPanel({
// topPadding: 1,
// afterDetachCSSClass: "floated-panel",
// savePanelSpace: true
//});
//this statement tells the browser to fade out any success message after 5 seconds
setTimeout(function(){$(".success").fadeOut("slow")}, 5000);
});
/*
* i18n_months and i18n_days_short are used in jquery datepickers
* which we use in multiple places
*/
var i18n_months = [
$.i18n._("January"),
$.i18n._("February"),
$.i18n._("March"),
$.i18n._("April"),
$.i18n._("May"),
$.i18n._("June"),
$.i18n._("July"),
$.i18n._("August"),
$.i18n._("September"),
$.i18n._("October"),
$.i18n._("November"),
$.i18n._("December")
];
var i18n_months_short = [
$.i18n._("Jan"),
$.i18n._("Feb"),
$.i18n._("Mar"),
$.i18n._("Apr"),
$.i18n._("May"),
$.i18n._("Jun"),
$.i18n._("Jul"),
$.i18n._("Aug"),
$.i18n._("Sep"),
$.i18n._("Oct"),
$.i18n._("Nov"),
$.i18n._("Dec")
];
var i18n_days_short = [
$.i18n._("Su"),
$.i18n._("Mo"),
$.i18n._("Tu"),
$.i18n._("We"),
$.i18n._("Th"),
$.i18n._("Fr"),
$.i18n._("Sa")
];
var HTTPMethods = Object.freeze({
GET: "GET",
POST: "POST",
PUT: "PUT",
PATCH: "PATCH",
DELETE: "DELETE",
OPTIONS: "OPTIONS"
});
var dateStartId = "#sb_date_start",
timeStartId = "#sb_time_start",
dateEndId = "#sb_date_end",
timeEndId = "#sb_time_end";
function getDatatablesStrings(overrideDict) {
var dict = {
"sEmptyTable": $.i18n._("No data available in table"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ entries"),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 entries"),
"sInfoFiltered": "", // $.i18n._("(filtered from _MAX_ total entries)"),
"sInfoPostFix": $.i18n._(""),
"sInfoThousands": $.i18n._(","),
"sLengthMenu": $.i18n._("Show _MENU_"),
"sLoadingRecords": $.i18n._("Loading..."),
//"sProcessing": $.i18n._("Processing..."),
"sProcessing": $.i18n._(""),
"sSearch": $.i18n._(""),
"sZeroRecords": $.i18n._("No matching records found"),
"oPaginate": {
"sFirst": "«",
"sLast": "»",
"sNext": "›",
"sPrevious": "‹"
},
//"oPaginate": {
// "sFirst": $.i18n._("First"),
// "sLast": $.i18n._("Last"),
// "sNext": $.i18n._("Next"),
// "sPrevious": $.i18n._("Previous")
//},
"oAria": {
"sSortAscending": $.i18n._(": activate to sort column ascending"),
"sSortDescending": $.i18n._(": activate to sort column descending")
}
};
return $.extend({}, dict, overrideDict);
}
function adjustDateToServerDate(date, serverTimezoneOffset){
//date object stores time in the browser's localtime. We need to artificially shift
//it to
var timezoneOffset = date.getTimezoneOffset()*60*1000;
date.setTime(date.getTime() + timezoneOffset + serverTimezoneOffset*1000);
/* date object has been shifted to artificial UTC time. Now let's
* shift it to the server's timezone */
return date;
}
/**
*handle to the jplayer window
*/
var _preview_window = null;
/**
*Gets the info from the view when menu action play choosen and opens the jplayer window.
*/
function openAudioPreview(p_event) {
p_event.stopPropagation();
var audioFileID = $(this).attr('audioFile');
var objId = $('.obj_id:first').attr('value');
var objType = $('.obj_type:first').attr('value');
var playIndex = $(this).parent().parent().attr('id');
playIndex = playIndex.substring(4); //remove the spl_
if (objType == "playlist") {
open_playlist_preview(objId, playIndex);
} else if (objType == "block") {
open_block_preview(objId, playIndex);
}
}
function open_audio_preview(type, id) {
// The reason that we need to encode artist and title string is that
// sometime they contain '/' or '\' and apache reject %2f or %5f
// so the work around is to encode it twice.
openPreviewWindow(baseUrl+'audiopreview/audio-preview/audioFileID/'+id+'/type/'+type, previewWidth, previewHeight);
_preview_window.focus();
}
/**
*Opens a jPlayer window for the specified info, for either an audio file or playlist.
*If audioFile, audioFileTitle, audioFileArtist is supplied the jplayer opens for one file
*Otherwise the playlistID and playlistIndex was supplied and a playlist is played starting with the
*given index.
*/
function open_playlist_preview(p_playlistID, p_playlistIndex) {
if (p_playlistIndex == undefined) //Use a resonable default.
p_playlistIndex = 0;
if (_preview_window != null && !_preview_window.closed)
_preview_window.playAllPlaylist(p_playlistID, p_playlistIndex);
else
openPreviewWindow(baseUrl+'audiopreview/playlist-preview/playlistIndex/'+p_playlistIndex+'/playlistID/'+p_playlistID, previewWidth, previewHeight);
_preview_window.focus();
}
function open_block_preview(p_blockId, p_blockIndex) {
if (p_blockIndex == undefined) //Use a resonable default.
p_blockIndex = 0;
if (_preview_window != null && !_preview_window.closed)
_preview_window.playBlock(p_blockId, p_blockIndex);
else
openPreviewWindow(baseUrl+'audiopreview/block-preview/blockIndex/'+p_blockIndex+'/blockId/'+p_blockId, previewWidth, previewHeight);
_preview_window.focus();
}
/**
*Opens a jPlayer window for the specified info, for either an audio file or playlist.
*If audioFile, audioFileTitle, audioFileArtist is supplied the jplayer opens for one file
*Otherwise the playlistID and playlistIndex was supplied and a playlist is played starting with the
*given index.
*/
function open_show_preview(p_showID, p_showIndex) {
if (_preview_window != null && !_preview_window.closed)
_preview_window.playAllShow(p_showID, p_showIndex);
else
openPreviewWindow(baseUrl+'audiopreview/show-preview/showID/'+p_showID+'/showIndex/'+p_showIndex, previewWidth, previewHeight);
_preview_window.focus();
}
function openPreviewWindow(url, w, h) {
var dim = (w && h) ? 'width=' + w + ',height=' + h + ',' : '';
// Hardcoding this here is kinda gross, but the alternatives aren't much better...
_preview_window = window.open(url, $.i18n._('Audio Player'), dim + 'scrollbars=yes');
return false;
}
function validateTimeRange() {
var oRange,
inputs = $('.sb-timerange > input'),
start, end;
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
start = oRange.start;
end = oRange.end;
if (end >= start) {
inputs.removeClass('error');
} else {
if (!inputs.hasClass('error')) {
inputs.addClass('error');
}
}
return {
start: start,
end: end,
isValid: end >= start
};
}
// validate uploaded images
function validateImage(img, el) {
// remove any existing error messages
if ($("#img-err")) { $("#img-err").remove(); }
if (img.size > 2048000) { // 2MB - pull this from somewhere instead?
// hack way of inserting an error message
var err = $.i18n._("Selected file is too large");
el.parent().after(
"<ul id='img-err' class='errors'>" +
"<li>" + err + "</li>" +
"</ul>");
return false;
} else if (validateMimeType(img.type) < 0) {
var err = $.i18n._("File format is not supported");
el.parent().after(
"<ul id='img-err' class='errors'>" +
"<li>" + err + "</li>" +
"</ul>");
return false;
}
return true;
}
// validate image mime type
function validateMimeType(mime) {
var extensions = [
'image/jpeg',
'image/png',
'image/gif'
// BMP?
];
return $.inArray(mime, extensions);
}
function pad(number, length) {
return sprintf("%'0"+length+"d", number);
}
function removeSuccessMsg() {
var $status = $('.success');
$status.fadeOut("slow", function(){$status.empty()});
}
function hideHint(h) {
h.hide("slow").addClass("hidden");
}
function showHint(h) {
h.show("slow").removeClass("hidden");
}
function getUsabilityHint() {
var pathname = window.location.pathname;
$.getJSON(baseUrl + "api/get-usability-hint", {"format": "json", "userPath": pathname}, function(json) {
var $hint_div = $('.usability_hint');
var current_hint = $hint_div.html();
if (json === "") {
// there are no more hints to display to the user
hideHint($hint_div);
} else if (current_hint !== json) {
// we only change the message if it is new
if ($hint_div.is(":visible")) {
hideHint($hint_div);
}
$hint_div.html(json);
showHint($hint_div);
} else {
// hint is the same before we hid it so we just need to show it
if ($hint_div.is(":hidden")) {
showHint($hint_div);
}
}
});
}
// TODO: build this out so we can use it as a fallback in fail cases
function buildErrorDialog(message) {
var el = $("<div id='error_dialog'></div>");
el.text(message);
$(document.body).append(el);
$("#error_dialog").dialog({
title: $.i18n._("Something went wrong!"),
resizable: false,
modal: true,
width: "auto",
height: "auto"
});
}
/**
* Add title attributes (whose values are their inner text) to all elements in the calling parent matching selector
*
* @param selector jQuery selector to search descendants
* @returns {jQuery}
*/
jQuery.fn.addTitles = function(selector) {
this.each(function() {
// Put this in a mouseenter event handler so it's dynamic
// (newly created elements will have the title applied on hover)
$(this).on("mouseenter", selector, function () {
$(this).attr("title", $(this).text());
});
});
return this; // jQuery chaining
};
// XXX: Old code to pan selector text; keeping this around in case we want to use it later - Duncan
jQuery.fn.scrollText = function(selector) {
this.each(function () {
$(this).on("mouseenter", selector, function () {
var sw = $(this)[0].scrollWidth - parseFloat($(this).css("textIndent")), iw = $(this).innerWidth();
if (sw > iw) {
$(this).stop().animate({
textIndent: "-" + (sw + 1 - iw) + "px"
}, sw * 8);
}
});
$(this).on("mouseleave", selector, function () {
$(this).stop().animate({
textIndent: "0"
}, 500);
});
});
return this;
};

View file

@ -0,0 +1,89 @@
$(document).ready(function() {
$("#facebook-login").click(function() {
AIRTIME.facebook.promptForFacebookPage();
});
});
window.fbAsyncInit = function() {
FB.init({
appId : FACEBOOK_APP_ID,
xfbml : true,
version : 'v2.4'
});
};
var AIRTIME = (function(AIRTIME) {
//Module initialization
if (AIRTIME.facebook === undefined) {
AIRTIME.facebook = {};
}
var mod = AIRTIME.facebook;
(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
mod.promptForFacebookPage = function() {
FB.login(function (response) {
if (response.authResponse) {
mod.getPagesOwnedByUser(response.authResponse.userID, response.authResponse.accessToken);
mod.addPageTab();
} else {
console.log('Authorization failed.');
}
}, {scope: 'manage_pages'});
}
mod.getPagesOwnedByUser = function(userId, accessToken) {
FB.api('/' + userId + '/accounts', function (response) {
console.log(response);
}, {access_token: accessToken});
}
mod.addPageTab = function() {
FB.ui(
{ method: 'pagetab' },
function (resp) {
console.log("response:");
console.log(resp);
var pageIdList = [];
var tabs = resp["tabs_added"];
if ((tabs != undefined) && (Object.keys(tabs).length > 0)) {
for (var pageId in tabs) {
pageIdList.push(pageId);
}
//POST these back to Airtime, which will then proxy it over to our social app. (multiple requests from Airtime)
$.post('facebook-tab-success', { "pages" : JSON.stringify(pageIdList) }, function() {
alert("Successfully added to your Facebook page!");
}).done(function() {
}).fail(function() {
alert("Sorry, an error occurred and we were unable to add the widget to your Facebook page.");
});
}
},
{
app_id: FACEBOOK_APP_ID,
//redirect_uri: 'https://localhost'
}
);
}
return AIRTIME;
}(AIRTIME || {}));

View file

@ -0,0 +1,530 @@
//approximate server time, because once we receive it from the server,
//there way have been a great amount of latency and it is no longer accurate.
var approximateServerTime = null;
var localRemoteTimeOffset = null;
var previousSong = null;
var currentSong = null;
var nextSong = null;
var currentShow = new Array();
var nextShow = new Array();
var showName = null;
var currentElem;
var serverUpdateInterval = 5000;
var uiUpdateInterval = 200;
var master_dj_on_air = false;
var live_dj_on_air = false;
var scheduled_play_on_air = false;
var scheduled_play_source = false;
//a reference returned by setTimeout. Useful for when we want clearTimeout()
var newSongTimeoutId = null;
//a reference returned by setTimeout. Useful for when we want clearTimeout()
var newShowTimeoutId = null;
//keep track of how many UI refreshes the ON-AIR light has been off for.
//For example, the uiUpdateInterval is every 200ms, so if onAirOffIterations
//is 25, then that means 5 seconds have gone by.
var onAirOffIterations = 0;
/* boolean flag to let us know if we should prepare to execute a function
* that flips the playlist to the next song. This flag's purpose is to
* make sure the function is only executed once*/
var nextSongPrepare = true;
var nextShowPrepare = true;
function secondsTimer(){
/* This function constantly calls itself every 'uiUpdateInterval'
* micro-seconds and is responsible for updating the UI. */
if (localRemoteTimeOffset !== null){
var date = new Date();
approximateServerTime = date.getTime() - localRemoteTimeOffset;
updateProgressBarValue();
updatePlaybar();
controlOnAirLight();
controlSwitchLight();
}
setTimeout(secondsTimer, uiUpdateInterval);
}
function newSongStart(){
nextSongPrepare = true;
if (nextSong.type == 'track') {
currentSong = nextSong;
nextSong = null;
}
}
function nextShowStart(){
nextShowPrepare = true;
currentShow[0] = nextShow.shift();
}
/* Called every "uiUpdateInterval" mseconds. */
function updateProgressBarValue(){
var showPercentDone = 0;
if (currentShow.length > 0){
showPercentDone = (approximateServerTime - currentShow[0].showStartPosixTime)/currentShow[0].showLengthMs*100;
if (showPercentDone < 0 || showPercentDone > 100){
showPercentDone = 0;
currentShow = new Array();
currentSong = null;
}
}
$('#progress-show').attr("style", "width:"+showPercentDone+"%");
var songPercentDone = 0;
var scheduled_play_div = $("#scheduled_play_div");
var scheduled_play_line_to_switch = scheduled_play_div.parent().find(".line-to-switch");
if (currentSong !== null){
var songElapsedTime = 0;
songPercentDone = (approximateServerTime - currentSong.songStartPosixTime)/currentSong.songLengthMs*100;
songElapsedTime = approximateServerTime - currentSong.songStartPosixTime;
if (songPercentDone < 0) {
songPercentDone = 0;
//currentSong = null;
} else if (songPercentDone > 100) {
songPercentDone = 100;
} else {
if ((currentSong.media_item_played == true && currentShow.length > 0) || (songElapsedTime < 5000 && currentShow[0].record != 1)) {
scheduled_play_line_to_switch.attr("class", "line-to-switch on");
scheduled_play_div.addClass("ready");
scheduled_play_source = true;
}
else{
scheduled_play_source = false;
scheduled_play_line_to_switch.attr("class", "line-to-switch off");
scheduled_play_div.removeClass("ready");
}
$('#progress-show').attr("class", "progress-show");
}
} else {
scheduled_play_source = false;
scheduled_play_line_to_switch.attr("class", "line-to-switch off");
scheduled_play_div.removeClass("ready");
$('#progress-show').attr("class", "progress-show-error");
}
$('#progress-bar').attr("style", "width:"+songPercentDone+"%");
}
function updatePlaybar(){
/* Column 0 update */
if (previousSong !== null){
$('#previous').text(previousSong.name+",");
$('#prev-length').text(convertToHHMMSSmm(previousSong.songLengthMs));
}else{
$('#previous').empty();
$('#prev-length').empty();
}
if (currentSong !== null && !master_dj_on_air && !live_dj_on_air){
if (currentSong.record == "1") {
$('#current').html("<span style='color:red; font-weight:bold'>"+$.i18n._("Recording:")+"</span>"+currentSong.name+",");
} else {
$('#current').text(currentSong.name+",");
if (currentSong.metadata && currentSong.metadata.artwork_data) {
var check_current_song = Cookies.get('current_track');
var loaded = Cookies.get('loaded');
if (check_current_song != currentSong.name) {
$('#now-playing-artwork_containter').html("<img height='75' width='75' class'artwork' src='"+ currentSong.metadata.artwork_data +"' />");
Cookies.remove('current_track');
Cookies.set('current_track', currentSong.name);
}
// makes sure it stays updated with current track if page loads
if (loaded != UNIQID) {
Cookies.remove('current_track');
Cookies.remove('loaded');
Cookies.set('loaded', UNIQID);
}
}
}
}else{
if (master_dj_on_air) {
if (showName) {
$('#current').html($.i18n._("Current")+": <span style='color:red; font-weight:bold'>"+showName+" - "+$.i18n._("Master Stream")+"</span>");
} else {
$('#current').html($.i18n._("Current")+": <span style='color:red; font-weight:bold'>"+$.i18n._("Master Stream")+"</span>");
}
} else if (live_dj_on_air) {
if (showName) {
$('#current').html($.i18n._("Current")+": <span style='color:red; font-weight:bold'>"+showName+" - "+$.i18n._("Live Stream")+"</span>");
} else {
$('#current').html($.i18n._("Current")+": <span style='color:red; font-weight:bold'>"+$.i18n._("Live Stream")+"</span>");
}
} else {
$('#current').html($.i18n._("Current")+": <span style='color:red; font-weight:bold'>"+$.i18n._("Nothing Scheduled")+"</span>");
}
}
if (nextSong !== null){
$('#next').text(nextSong.name+",");
$('#next-length').text(convertToHHMMSSmm(nextSong.songLengthMs));
}else{
$('#next').empty();
$('#next-length').empty();
}
$('#start').empty();
$('#end').empty();
$('#time-elapsed').empty();
$('#time-remaining').empty();
$('#song-length').empty();
if (currentSong !== null && !master_dj_on_air && !live_dj_on_air){
$('#start').text(currentSong.starts.split(' ')[1]);
$('#end').text(currentSong.ends.split(' ')[1]);
/* Get rid of the millisecond accuracy so that the second counters for both
* show and song change at the same time. */
var songStartRoughly = parseInt(Math.round(currentSong.songStartPosixTime/1000), 10)*1000;
var songEndRoughly = parseInt(Math.round(currentSong.songEndPosixTime/1000), 10)*1000;
$('#time-elapsed').text(convertToHHMMSS(approximateServerTime - songStartRoughly));
$('#time-remaining').text(convertToHHMMSS(songEndRoughly - approximateServerTime));
$('#song-length').text(convertToHHMMSS(currentSong.songLengthMs));
}
/* Column 1 update */
$('#playlist').text($.i18n._("Current Show:"));
var recElem = $('.recording-show');
if (currentShow.length > 0){
$('#playlist').text(currentShow[0].name);
(currentShow[0].record == "1") ? recElem.show(): recElem.hide();
} else {
recElem.hide();
}
$('#show-length').empty();
if (currentShow.length > 0){
$('#show-length').text(convertDateToHHMM(currentShow[0].showStartPosixTime) + " - " + convertDateToHHMM(currentShow[0].showEndPosixTime));
}
/* Column 2 update */
$('#time').text(convertDateToHHMMSS(approximateServerTime));
}
function calcAdditionalData(currentItem){
currentItem.songStartPosixTime = convertDateToPosixTime(currentItem.starts);
currentItem.songEndPosixTime = convertDateToPosixTime(currentItem.ends);
currentItem.songLengthMs = currentItem.songEndPosixTime - currentItem.songStartPosixTime;
}
function calcAdditionalShowData(show){
if (show.length > 0){
show[0].showStartPosixTime = convertDateToPosixTime(show[0].start_timestamp);
show[0].showEndPosixTime = convertDateToPosixTime(show[0].end_timestamp);
show[0].showLengthMs = show[0].showEndPosixTime - show[0].showStartPosixTime;
}
}
function calculateTimeToNextSong() {
if (approximateServerTime === null) {
return;
}
if (newSongTimeoutId !== null) {
/* We have a previous timeout set, let's unset it */
clearTimeout(newSongTimeoutId);
newSongTimeoutId = null;
}
var diff = nextSong.songStartPosixTime - approximateServerTime;
if (diff < 0) diff=0;
nextSongPrepare = false;
newSongTimeoutId= setTimeout(newSongStart, diff);
}
function calculateTimeToNextShow() {
if (approximateServerTime === null) {
return;
}
if (newShowTimeoutId !== null) {
/* We have a previous timeout set, let's unset it */
clearTimeout(newShowTimeoutId);
newShowTimeoutId = null;
}
var diff = nextShow[0].showStartPosixTime - approximateServerTime;
if (diff < 0) diff=0;
nextShowPrepare = false;
newShowTimeoutId= setTimeout(nextShowStart, diff);
}
function parseItems(obj){
previousSong = obj.previous;
currentSong = obj.current;
nextSong = obj.next;
if (previousSong !== null) {
calcAdditionalData(previousSong);
}
if (currentSong !== null) {
calcAdditionalData(currentSong);
}
if (nextSong !== null) {
calcAdditionalData(nextSong);
calculateTimeToNextSong();
}
currentShow = new Array();
if (obj.currentShow.length > 0) {
calcAdditionalShowData(obj.currentShow);
currentShow = obj.currentShow;
}
nextShow = new Array();
if (obj.nextShow.length > 0) {
calcAdditionalShowData(obj.nextShow);
nextShow = obj.nextShow;
calculateTimeToNextShow();
}
var schedulePosixTime = convertDateToPosixTime(obj.schedulerTime);
var date = new Date();
localRemoteTimeOffset = date.getTime() - schedulePosixTime;
}
function parseSourceStatus(obj){
var live_div = $("#live_dj_div");
var master_div = $("#master_dj_div");
var live_li = live_div.parent();
var master_li = master_div.parent();
if(obj.live_dj_source == false){
live_li.find(".line-to-switch").attr("class", "line-to-switch off");
live_div.removeClass("ready");
}else{
live_li.find(".line-to-switch").attr("class", "line-to-switch on");
live_div.addClass("ready");
}
if(obj.master_dj_source == false){
master_li.find(".line-to-switch").attr("class", "line-to-switch off");
master_div.removeClass("ready");
}else{
master_li.find(".line-to-switch").attr("class", "line-to-switch on");
master_div.addClass("ready");
}
}
function parseSwitchStatus(obj){
if(obj.live_dj_source == "on"){
live_dj_on_air = true;
}else{
live_dj_on_air = false;
}
if(obj.master_dj_source == "on"){
master_dj_on_air = true;
}else{
master_dj_on_air = false;
}
if(obj.scheduled_play == "on"){
scheduled_play_on_air = true;
}else{
scheduled_play_on_air = false;
}
var scheduled_play_switch = $("#scheduled_play.source-switch-button");
var live_dj_switch = $("#live_dj.source-switch-button");
var master_dj_switch = $("#master_dj.source-switch-button");
scheduled_play_switch.find("span").html(obj.scheduled_play);
if(scheduled_play_on_air){
scheduled_play_switch.addClass("active");
}else{
scheduled_play_switch.removeClass("active");
}
live_dj_switch.find("span").html(obj.live_dj_source);
if(live_dj_on_air){
live_dj_switch.addClass("active");
}else{
live_dj_switch.removeClass("active");
}
master_dj_switch.find("span").html(obj.master_dj_source)
if(master_dj_on_air){
master_dj_switch.addClass("active");
}else{
master_dj_switch.removeClass("active");
}
}
function controlOnAirLight(){
if ((scheduled_play_on_air && scheduled_play_source) || live_dj_on_air || master_dj_on_air) {
$('#on-air-info').attr("class", "on-air-info on");
onAirOffIterations = 0;
} else if (onAirOffIterations < 20) {
//if less than 4 seconds have gone by (< 20 executions of this function)
//then keep the ON-AIR light on. Only after at least 3 seconds have gone by,
//should we be allowed to turn it off. This is to stop the light from temporarily turning
//off between tracks: CC-3725
onAirOffIterations++;
} else {
$('#on-air-info').attr("class", "on-air-info off");
}
}
function controlSwitchLight(){
var live_li= $("#live_dj_div").parent();
var master_li = $("#master_dj_div").parent();
var scheduled_play_li = $("#scheduled_play_div").parent();
if((scheduled_play_on_air && scheduled_play_source) && !live_dj_on_air && !master_dj_on_air){
scheduled_play_li.find(".line-to-on-air").attr("class", "line-to-on-air on");
live_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
master_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
}else if(live_dj_on_air && !master_dj_on_air){
scheduled_play_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
live_li.find(".line-to-on-air").attr("class", "line-to-on-air on");
master_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
}else if(master_dj_on_air){
scheduled_play_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
live_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
master_li.find(".line-to-on-air").attr("class", "line-to-on-air on");
}else{
scheduled_play_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
live_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
master_li.find(".line-to-on-air").attr("class", "line-to-on-air off");
}
}
function getScheduleFromServer(){
$.ajax({ url: baseUrl+"Schedule/get-current-playlist/format/json",
dataType:"json",
success:function(data){
parseItems(data.entries);
parseSourceStatus(data.source_status);
parseSwitchStatus(data.switch_status);
showName = data.show_name;
}, error:function(jqXHR, textStatus, errorThrown){}});
}
function setupQtip(){
var qtipElem = $('#about-link');
if (qtipElem.length > 0){
qtipElem.qtip({
content: $('#about-txt').html(),
show: 'mouseover',
hide: { when: 'mouseout', fixed: true },
position: {
corner: {
target: 'center',
tooltip: 'topRight'
}
},
style: {
border: {
width: 0,
radius: 4
},
name: 'light' // Use the default light style
}
});
}
}
function setSwitchListener(ele){
var sourcename = $(ele).attr('id');
var status_span = $(ele).find("span");
var status = status_span.html();
$.get(baseUrl+"Dashboard/switch-source/format/json/sourcename/"+sourcename+"/status/"+status, function(data){
if(data.error){
alert(data.error);
}else{
if(data.status == "ON"){
$(ele).addClass("active");
}else{
$(ele).removeClass("active");
}
status_span.html(data.status);
}
});
}
function kickSource(ele){
var sourcename = $(ele).attr('id');
$.get(baseUrl+"Dashboard/disconnect-source/format/json/sourcename/"+sourcename, function(data){
if(data.error){
alert(data.error);
}
});
}
var stream_window = null;
function init() {
//begin producer "thread"
setInterval(getScheduleFromServer, serverUpdateInterval);
//begin consumer "thread"
secondsTimer();
setupQtip();
$('.listen-control-button').click(function() {
if (stream_window == null || stream_window.closed)
stream_window=window.open(baseUrl+"Dashboard/stream-player", 'name', 'width=400,height=158');
stream_window.focus();
return false;
});
}
/* We never retrieve the user's password from the db
* and when we call isValid($params) the form values are cleared
* and repopulated with $params which does not have the password
* field. Therefore, we fill the password field with 6 x's
*/
function setCurrentUserPseudoPassword() {
$('#cu_password').val("xxxxxx");
$('#cu_passwordVerify').val("xxxxxx");
}
/*$(window).resize(function() {
*//* If we don't do this, the menu can stay hidden after resizing *//*
if ($(this).width() > 970) {
$("#nav .responsive-menu").show();
}
});*/
$(document).ready(function() {
if ($('#master-panel').length > 0)
init();
if ($('.errors').length === 0) {
setCurrentUserPseudoPassword();
}
$('body').on('click','#current-user', function() {
$.ajax({
url: baseUrl+'user/edit-user/format/json'
});
});
$('body').on('click', '#cu_save_user', function() {
Cookies.set('airtime_locale', $('#cu_locale').val(), {path: '/'});
});
// When the 'Listen' button is clicked we set the width
// of the share button to the width of the 'Live Stream'
// text. This differs depending on the language setting
$('#popup-link').css('width', $('.jp-container h1').css('width'));
/*$('#menu-btn').click(function() {
$('#nav .responsive-menu').slideToggle();
});*/
});

View file

@ -0,0 +1,233 @@
/* function to create popup window */
function popup(mylink){
if (!window.focus)
return true;
var href;
if (typeof(mylink) == 'string')
href=mylink;
else
href=mylink.href;
window.open(href, "player", 'width=300,height=100,scrollbars=yes');
return false;
}
/* Take a string representing a date in the format 2012-04-25 and return
* a javascript date object representing this date. */
function getDateFromString(time){
var date = time.split("-");
if (date.length != 3){
return null;
}
var year = parseInt(date[0], 10);
var month = parseInt(date[1], 10) -1;
var day = parseInt(date[2], 10);
if (isNaN(year) || isNaN(month) || isNaN(day)){
return null;
}
return new Date(year, month, day);
}
function convertSecondsToDaysHoursMinutesSeconds(seconds){
if (seconds < 0)
seconds = 0;
seconds = parseInt(seconds, 10);
var days = parseInt(seconds / 86400);
seconds -= days*86400;
var hours = parseInt(seconds / 3600);
seconds -= hours*3600;
var minutes = parseInt(seconds / 60);
seconds -= minutes*60;
return {days:days, hours:hours, minutes:minutes, seconds:seconds};
}
/* Takes an input parameter of milliseconds and converts these into
* the format HH:MM:SS */
function convertToHHMMSS(timeInMS){
var time = parseInt(timeInMS);
var hours = parseInt(time / 3600000);
time -= 3600000*hours;
var minutes = parseInt(time / 60000);
time -= 60000*minutes;
var seconds = parseInt(time / 1000);
hours = hours.toString();
minutes = minutes.toString();
seconds = seconds.toString();
if (hours.length == 1)
hours = "0" + hours;
if (minutes.length == 1)
minutes = "0" + minutes;
if (seconds.length == 1)
seconds = "0" + seconds;
return hours + ":" + minutes + ":" + seconds;
}
function convertToHHMMSSmm(timeInMS){
var time = parseInt(timeInMS);
var hours = parseInt(time / 3600000);
time -= 3600000*hours;
var minutes = parseInt(time / 60000);
time -= 60000*minutes;
var seconds = parseInt(time / 1000);
time -= 1000*seconds;
var ms = parseInt(time);
hours = hours.toString();
minutes = minutes.toString();
seconds = seconds.toString();
ms = ms.toString();
if (hours.length == 1)
hours = "0" + hours;
if (minutes.length == 1)
minutes = "0" + minutes;
if (seconds.length == 1)
seconds = "0" + seconds;
if (ms.length == 3)
ms = ms.substring(0, 2);
else if (ms.length == 2)
ms = "0" + ms.substring(0,1);
else if (ms.length == 1)
ms = "00";
if (hours == "00")
return minutes + ":" + seconds + "." + ms;
else
return hours + ":" + minutes + ":" + seconds+ "." + ms;
}
function convertDateToHHMM(epochTime){
var d = new Date(epochTime);
var hours = d.getUTCHours().toString();
var minutes = d.getUTCMinutes().toString();
if (hours.length == 1)
hours = "0" + hours;
if (minutes.length == 1)
minutes = "0" + minutes;
return hours + ":" + minutes;
}
function convertDateToHHMMSS(epochTime){
var d = new Date(epochTime);
var hours = d.getUTCHours().toString();
var minutes = d.getUTCMinutes().toString();
var seconds = d.getUTCSeconds().toString();
if (hours.length == 1)
hours = "0" + hours;
if (minutes.length == 1)
minutes = "0" + minutes;
if (seconds.length == 1)
seconds = "0" + seconds;
return hours + ":" + minutes + ":" + seconds;
}
/* Takes in a string of format similar to 2011-02-07 02:59:57,
* and converts this to epoch/posix time. */
function convertDateToPosixTime(s){
var datetime = s.split(" ");
var date = datetime[0].split("-");
var time = datetime[1].split(":");
var year = date[0];
var month = date[1];
var day = date[2];
var hour = time[0];
var minute = time[1];
var sec = 0;
var msec = 0;
if (time[2].indexOf(".") != -1){
var temp = time[2].split(".");
sec = temp[0];
msec = temp[1];
} else
sec = time[2];
return Date.UTC(year, month-1, day, hour, minute, sec, msec);
}
function getFileExt(filename){
return filename.split('.').pop();
}
function resizeImg(ele, targetWidth, targetHeight){
var img = $(ele);
var width = ele.width;
var height = ele.height;
// resize img proportionaly
if( width > height && width > targetWidth){
var ratio = targetWidth/width;
img.css("width", targetHeight+"px");
var newHeight = height * ratio;
img.css("height", newHeight+"px");
}else if( width < height && height > targetHeight){
var ratio = targetHeight/height;
img.css("height", targetHeight+"px");
var newWidth = width * ratio;
img.css("width", newWidth+"px");
}else if( width == height && width > targetWidth){
img.css("height", targetHeight+"px");
img.css("width", targetWidth+"px" );
}
}
function resizeToMaxHeight(ele, targetHeight){
var img = $(ele);
var width = ele.width;
var height = ele.height;
// resize img proportionaly
if( height > targetHeight){
var ratio = targetHeight/height;
img.css("height", targetHeight+"px");
var newWidth = width * ratio;
img.css("width", newWidth+"px");
}
}
/* From http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/#7557433 */
function isInView(el) {
//special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
);
}

View file

@ -0,0 +1,150 @@
/**
* Get the tooltip message to be displayed
*/
function getContent() {
var link = getLatestLink();
var hasPatch = getHasPatch();
var hasMinor = getHasMinor();
var hasMajor = getHasMajor();
var hasMultiMajor = getHasMultiMajor();
var isPreRelease = getIsPreRelease();
var msg = "";
// See file airtime_mvc/application/views/helpers/VersionNotify.php for more info
if(isUpToDate()) {
msg = $.i18n._("You are running the latest version");
} else {
msg = $.i18n._("New version available: ") + link + '<ul>';
if (isPreRelease) {
msg += '<li>'+$.i18n._("You have a pre-release version of LibreTime intalled.");
}
if (hasPatch) {
msg += '<li>'+$.i18n._("A patch update for your LibreTime installation is available.");
}
if (hasMinor) {
msg += '<li>'+$.i18n._("A feature update for your LibreTime installation is available.");
}
if (hasMajor && !hasMultiMajor) {
msg += '<li>'+$.i18n._("A major update for your LibreTime installation is available.");
}
if (hasMultiMajor) {
msg += '<li>'+$.i18n._("Multiple major updates for LibreTime installation are available. Please upgrade as soon as possible.");
}
msg += '</ul>';
}
return msg;
}
/**
* Get if patch is available
*/
function getHasPatch() {
return versionNotifyInfo.hasPatch;
}
/**
* Get if minor update is available
*/
function getHasMinor() {
return versionNotifyInfo.hasMinor;
}
/**
* Get if major update is available
*/
function getHasMajor() {
return versionNotifyInfo.hasMajor;
}
/**
* Get if multiple major updates are available
*/
function getHasMultiMajor() {
return versionNotifyInfo.hasMultiMajor;
}
/**
* Get if pre-release was installed
*/
function getIsPreRelease() {
return versionNotifyInfo.isPreRelease;
}
/**
* Get the current version
*/
function getCurrentVersion() {
return versionNotifyInfo.current;
}
/**
* Get the latest version
*/
function getLatestVersion() {
return versionNotifyInfo.latest;
}
/**
* Returns the download link to latest release in HTML
*/
function getLatestLink() {
return "<a href='' onclick='openLatestLink();'>" + getLatestVersion() + "</a>";
}
/**
* Returns true if current version is up to date
*/
function isUpToDate() {
return !getHasPatch() && !getHasMinor() && !getHasMajor();
}
/**
* Opens the link in a new window
*/
function openLatestLink() {
window.open(versionNotifyInfo.link);
}
/**
* Sets up the tooltip for version notification
*/
function setupVersionQtip(){
var qtipElem = $('#version-icon');
if (qtipElem.length > 0){
qtipElem.qtip({
id: 'version',
content: {
text: getContent(),
title: {
text: getCurrentVersion(),
button: isUpToDate() ? false : true
}
},
hide: {
event: isUpToDate() ? 'mouseleave' : 'unfocus'
},
position: {
my: "top right",
at: "bottom left"
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
}
});
}
}
$(document).ready(function() {
if($('#version-icon').length > 0) {
setupVersionQtip();
}
});

View file

@ -0,0 +1,183 @@
var AIRTIME = (function(AIRTIME) {
var mod;
if (AIRTIME.library === undefined) {
AIRTIME.library = {};
}
mod = AIRTIME.library;
mod.checkAddButton = function() {
var selected = mod.getChosenItemsLength(),
sortable = $('.spl_sortable:visible'),
check = false,
blockType = $('input[name=sp_type]:checked', '#smart-block-form').val();
// make sure audioclips are selected and a playlist or static block is currently open.
// static blocks have value of 0
// dynamic blocks have value of 1
if (selected !== 0 && (sortable.length !== 0 || blockType === "0")) {
check = true;
}
if (check === true) {
AIRTIME.button.enableButton("btn-group #library-plus", false);
} else {
AIRTIME.button.disableButton("btn-group #library-plus", false);
}
var objType = $('.obj_type').val(),
btnText;
if (objType === 'playlist') {
btnText = ' '+$.i18n._('Add to current playlist');
} else if (objType === 'block') {
btnText = ' '+$.i18n._('Add to current smart block');
} else {
btnText = ' '+$.i18n._('Add to current playlist');
}
AIRTIME.library.changeAddButtonText($('.btn-group #library-plus #lib-plus-text'), btnText);
};
mod.fnRowCallback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
var $nRow = $(nRow);
if (aData.ftype === "audioclip") {
$nRow.addClass("lib-audio");
$image = $nRow.find('td.library_type');
if (!isAudioSupported(aData.mime)) {
$image.html('<span class="ui-icon ui-icon-locked"></span>');
aData.image = '<span class="ui-icon ui-icon-locked"></span>';
}
} else if (aData.ftype === "stream") {
$nRow.addClass("lib-stream");
} else if (aData.ftype === "block") {
$nRow.addClass("lib-block");
} else {
$nRow.addClass("lib-pl");
}
$nRow.attr("id", aData["tr_id"]).data("aData", aData).data("screen",
"playlist");
if (aData["bl_type"] !== undefined) {
$nRow.attr("bl_type", aData["bl_type"]);
}
};
mod.fnDrawCallback = function() {
mod.redrawChosen();
mod.checkToolBarIcons();
$('#library_display tr.lib-audio, tr.lib-stream, tr.lib-pl, tr.lib-block')
.draggable(
{
helper : function() {
var $el = $(this), selected = mod
.getChosenAudioFilesLength(), container, message, li = $(".side_playlist ul[id='spl_sortable'] li:first"),
width = li.width(), height = 55;
if (width > 798) width = 798;
// dragging an element that has an unselected
// checkbox.
if (mod.isChosenItem($el) === false) {
selected++;
}
if (selected === 1) {
message = $.i18n._("Adding 1 Item");
} else {
message = sprintf($.i18n._("Adding %s Items"), selected);
}
container = $('<div class="helper"/>').append(
"<li/>").find("li").addClass(
"ui-state-default").append("<div/>")
.find("div").addClass(
"list-item-container").append(
message).end().width(width)
.height(height).end();
return container;
},
cursor : 'pointer',
cursorAt: {
top: 30,
left: 100
},
connectToSortable : '.spl_sortable'
});
};
mod.dblClickAdd = function(data, type) {
var i, aMediaIds = [];
// process selected files/playlists.
aMediaIds.push(new Array(data.id, data.ftype));
// check if a playlist/block is open before adding items
if ($('input[id="obj_type"]').val() == 'playlist'
|| $('input[id="obj_type"]').val() == 'block') {
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
}
};
mod.setupLibraryToolbar = function() {
var $toolbar = $(".lib-content .fg-toolbar:first");
mod.createToolbarButtons();
$toolbar.append($menu);
// add to playlist button
$toolbar
.find('.icon-plus').parent()
.click(function() {
if (AIRTIME.button.isDisabled('btn-group #library-plus') === true) {
return;
}
var aData = AIRTIME.library.getSelectedData(), i, temp, length, aMediaIds = [];
// process selected files/playlists.
for (i = 0, length = aData.length; i < length; i++) {
temp = aData[i];
if (temp.ftype === "audioclip"
|| temp.ftype === "block"
|| (temp.ftype === "stream" && $(
".obj_type").val() === "playlist")) {
aMediaIds.push(new Array(temp.id,
temp.ftype));
}
}
if (aMediaIds.length > 0) {
AIRTIME.playlist.fnAddItems(aMediaIds,
undefined, 'after');
} else {
if ($('.obj_type').val() == 'block') {
alert($.i18n._('You can only add tracks to smart blocks.'));
} else if ($('.obj_type').val() == 'playlist') {
alert($.i18n._('You can only add tracks, smart blocks, and webstreams to playlists.'));
}
}
});
// delete from library.
$toolbar.find('.icon-trash').parent().click(function() {
if (AIRTIME.button.isDisabled('icon-trash') === true) {
return;
}
AIRTIME.library.fnDeleteSelectedItems();
});
mod.createToolbarDropDown();
};
return AIRTIME;
}(AIRTIME || {}));

View file

@ -0,0 +1,391 @@
var AIRTIME = (function(AIRTIME) {
var mod;
if (AIRTIME.library === undefined) {
AIRTIME.library = {};
}
mod = AIRTIME.library;
mod.checkAddButton = function() {
var selected = mod.getChosenItemsLength(), $cursor = $('tr.sb-selected'), check = false,
shows = $('tr.sb-header'), current = $('tr.sb-current-show'),
// TODO: this is an ugly way of doing this... we should find a more robust way of checking which view we're in.
btnText = (window.location.href.toLowerCase().indexOf("schedule") > -1) ? $.i18n._('Add to show') : $.i18n._('Add to next show');
// make sure library items are selected and a cursor is selected.
if (selected !== 0) {
check = true;
}
var sortable = $(".spl_sortable");
if ($("#show_builder_table").is(":visible")) {
if (shows.length === 0) {
check = false;
}
if ($cursor.length !== 0) {
btnText = $.i18n._('Add after selected items');
} else if (current.length !== 0) {
btnText = $.i18n._('Add to current show');
}
} else if (sortable.length > 0 && sortable.is(":visible")) {
var objType = $('.active-tab .obj_type').val();
if (objType === 'block') {
btnText = $.i18n._('Add to current smart block');
} else {
btnText = $.i18n._('Add to current playlist');
}
} else {
check = false;
}
if (check) {
AIRTIME.button.enableButton("btn-group #library-plus", false);
} else {
AIRTIME.button.disableButton("btn-group #library-plus", false);
}
AIRTIME.library.changeAddButtonText($('.btn-group #library-plus #lib-plus-text'), btnText);
};
mod.fnRowCallback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
var $nRow = $(nRow);
if (aData.ftype === "audioclip") {
$nRow.addClass("lib-audio");
$image = $nRow.find('td.library_type');
if (!isAudioSupported(aData.mime)) {
$image.html('<span class="ui-icon ui-icon-locked"></span>');
aData.image = '<span class="ui-icon ui-icon-locked"></span>';
}
} else if (aData.ftype === "stream") {
$nRow.addClass("lib-stream");
} else {
$nRow.addClass("lib-pl");
}
$nRow.attr("id", aData["tr_id"]).data("aData", aData).data("screen",
"timeline");
};
/**
* Draw a placeholder for the given table to show if it has no data.
*
* @param {Object} table jQuery object containing the table DOM node
*/
mod.drawEmptyPlaceholder = function (table) {
var opts;
if (table instanceof AIRTIME.widgets.Table) {
opts = table.getEmptyPlaceholder();
table = table.getDatatable();
if (!table) {
return;
}
}
var emptyRow = table.find('tr:has(td.dataTables_empty)'),
wrapper = table.closest(".dataTables_wrapper"),
libEmpty = wrapper.find('.empty_placeholder');
if (emptyRow.length > 0) {
emptyRow.hide();
var mediaType = parseInt($('.media_type_selector.selected').data('selection-id')),
img = wrapper.find('.empty_placeholder_image');
if (!opts && isNaN(mediaType)) {
return;
}
// Remove all classes for when we change between empty media types
img.removeClass(function() { return $(this).attr("class"); });
if (opts) {
img.addClass("empty_placeholder_image " + opts.iconClass);
wrapper.find('.empty_placeholder_text').html(opts.html);
} else {
opts = AIRTIME.library.placeholder(mediaType);
img.addClass("empty_placeholder_image icon-white " + opts.icon);
wrapper.find('.empty_placeholder_text').html(
$.i18n._("You haven't added any " + opts.media)
+ "<br/>" + $.i18n._(opts.subtext)
+ "<br/><a target='_blank' href='" + opts.href + "'>" + $.i18n._("Learn about " + opts.media) + "</a>"
);
}
libEmpty.show();
} else {
libEmpty.hide();
}
};
mod.fnDrawCallback = function fnLibDrawCallback() {
var table = $('#library_display'),
cb = table.find('th[class*="checkbox"]');
if (cb.find("input").length == 0) {
cb.append("<input id='super-checkbox' type='checkbox'>");
}
mod.redrawChosen();
mod.checkToolBarIcons();
mod.drawEmptyPlaceholder(table);
var sortable;
if ($("#show_builder_table").is(":visible")) {
sortable = "#show_builder_table";
} else {
sortable = ".active-tab .spl_sortable";
}
$('#library_display tr[class*="lib-"]')
.draggable(
{
helper: function () {
var $el = $(this), selected = mod
.getChosenItemsLength(), container, thead = $("#show_builder_table thead"), colspan = thead
.find("th").length, width = $el.width(), message;
// dragging an element that has an unselected
// checkbox.
if (mod.isChosenItem($el) === false) {
selected++;
}
if (selected === 1) {
message = $.i18n._("Adding 1 Item");
} else {
message = sprintf($.i18n._("Adding %s Items"), selected);
}
container = $('<div/>').attr('id',
'draggingContainer').append('<tr/>')
.find("tr").append('<td/>').find("td")
.attr("colspan", colspan).width(width)
.addClass("ui-state-highlight").append(
message).end().end();
return container;
},
create: function(event, ui) {
$(this).draggable("option", "cursorAt", {
top: 20,
left: Math.floor($(this).outerWidth() / 2)
});
},
tolerance: 'pointer',
cursor: 'move',
distance: 25, // min-distance for dragging
connectToSortable: sortable
});
};
mod.dblClickAdd = function(data, type) {
var i, length, temp, aMediaIds = [], aSchedIds = [], aData = [];
if ($("#show_builder_table").is(":visible")) {
// process selected files/playlists.
aMediaIds.push({
"id": data.id,
"type": type
});
$("#show_builder_table tr.sb-selected").each(function (i, el) {
aData.push($(el).data("aData"));
});
// process selected schedule rows to add media after.
for (i = 0, length = aData.length; i < length; i++) {
temp = aData[i];
aSchedIds.push({
"id": temp.id,
"instance": temp.instance,
"timestamp": temp.timestamp
});
}
if (aSchedIds.length == 0) {
if (!addToCurrentOrNext(aSchedIds)) {
return;
}
}
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
} else {
// process selected files/playlists.
aMediaIds.push(new Array(data.id, data.ftype));
// check if a playlist/block is open before adding items
if ($('.active-tab .obj_type').val() == 'playlist'
|| $('.active-tab .obj_type').val() == 'block') {
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
}
}
};
function addToCurrentOrNext(arr) {
var el;
// Add to the end of the current or next show by getting the footer
el = $(".sb-footer.sb-future:first");
var data = el.prev().data("aData");
if (data === undefined) {
alert($.i18n._("Cannot schedule outside a show.\nTry creating a show first."));
return false;
}
arr.push({
"id" : data.id,
"instance" : data.instance,
"timestamp" : data.timestamp
});
if (!isInView(el)) {
$('.dataTables_scrolling.sb-padded').animate({
scrollTop: el.offset().top
}, 0);
}
return true;
}
mod.addToSchedule = function (selected) {
console.log(selected);
var aMediaIds = [], aSchedIds = [], aData = [];
$.each(selected, function () {
aMediaIds.push({
"id": this.id,
"type": this.ftype
});
});
// process selected files/playlists.
$("#show_builder_table").find("tr.sb-selected").each(function (i, el) {
aData.push($(el).data("aData"));
});
// process selected schedule rows to add media after.
$.each(aData, function () {
aSchedIds.push({
"id": this.id,
"instance": this.instance,
"timestamp": this.timestamp
});
});
if (aSchedIds.length == 0) {
if (!addToCurrentOrNext(aSchedIds)) {
return;
}
}
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
};
mod.setupLibraryToolbar = function() {
var $toolbar = $(".lib-content .fg-toolbar:first");
mod.createToolbarButtons();
//mod.moveSearchBarToHeader();
$("#advanced_search").click(function(e) {
e.stopPropagation();
});
if (localStorage.getItem('user-type') != 'G') {
$toolbar.append($menu);
// add to timeline button
$toolbar
.find('#library-plus')
.click(
function () {
if (AIRTIME.button.isDisabled('btn-group #library-plus') === true) {
return;
}
var selected = AIRTIME.library.getSelectedData(), aMediaIds = [];
if ($("#show_builder_table").is(":visible")) {
mod.addToSchedule(selected);
} else {
$.each(selected, function () {
aMediaIds.push([this.id, this.ftype]);
});
// check if a playlist/block is open before adding items
if ($('.active-tab .obj_type').val() == 'playlist'
|| $('.active-tab .obj_type').val() == 'block') {
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
}
}
});
$toolbar.find('#publish-btn').click(function () {
if (AIRTIME.button.isDisabled('btn-group #publish-btn') === true) {
return;
}
var selected = $(".lib-selected");
selected.each(function (i, el) {
var data = $(el).data("aData");
AIRTIME.publish.openPublishDialog(data.id);
});
});
// delete from library.
$toolbar.find('#sb-delete').click(function () {
if (AIRTIME.button.isDisabled('btn-group #sb-delete') === true) {
return;
}
AIRTIME.library.fnDeleteSelectedItems();
});
$toolbar.find('#sb-new').click(function () {
if (AIRTIME.button.isDisabled('btn-group #sb-new') === true) {
return;
}
var selection = $(".media_type_selector.selected").data("selection-id");
if (selection == AIRTIME.library.MediaTypeIntegerEnum.PLAYLIST) {
AIRTIME.playlist.fnNew();
} else if (selection == AIRTIME.library.MediaTypeIntegerEnum.BLOCK) {
AIRTIME.playlist.fnNewBlock();
} else if (selection == AIRTIME.library.MediaTypeIntegerEnum.WEBSTREAM) {
AIRTIME.playlist.fnWsNew();
}
});
$toolbar.find('#sb-edit').click(function () {
if (AIRTIME.button.isDisabled('btn-group #sb-edit') === true) {
return;
}
var selected = $(".lib-selected");
selected.each(function (i, el) {
var data = $(el).data("aData");
if (data.ftype === "audioclip") {
$.get(baseUrl + "library/edit-file-md/id/" + data.id, {format: "json"}, function (json) {
AIRTIME.playlist.fileMdEdit(json, data.tr_id);
//buildEditMetadataDialog(json);
});
} else if (data.ftype === "playlist" || data.ftype === "block") {
AIRTIME.playlist.fnEdit(data, baseUrl + 'playlist/edit');
AIRTIME.playlist.validatePlaylistElements();
} else if (data.ftype === "stream") {
AIRTIME.playlist.fnEdit(data, baseUrl + 'webstream/edit');
}
});
});
mod.createToolbarDropDown();
}
};
return AIRTIME;
}(AIRTIME || {}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,219 @@
$(document).ready(function () {
var uploadProgress;
var self = this;
self.uploadFilter = "all";
self.IMPORT_STATUS_CODES = {
0: {message: $.i18n._("Successfully imported")},
1: {message: $.i18n._("Pending import")},
2: {message: $.i18n._("Import failed.")},
UNKNOWN: {message: $.i18n._("Unknown")}
};
if (Object.freeze) {
Object.freeze(self.IMPORT_STATUS_CODES);
}
Dropzone.options.addMediaDropzone = {
url: '/rest/media',
//clickable: false,
acceptedFiles: acceptedMimeTypes.join(),
addRemoveLinks: true,
dictRemoveFile: $.i18n._("Remove"),
maxFilesize:LIBRETIME_PLUPLOAD_MAX_FILE_SIZE, //Megabytes
init: function () {
this.on("sending", function (file, xhr, data) {
data.append("csrf_token", $("#csrf").val());
});
this.on("addedfile", function (file, xhr, data) {
var el = $(file.previewElement);
uploadProgress = true;
el.find(".dz-remove").prependTo(el.find(".dz-details"));
el.find(".dz-error-message").appendTo(el.find(".dz-error-mark"));
});
this.on("success", function (file, xhr, data) {
//Refresh the upload table:
self.recentUploadsTable.fnDraw(); //Only works because we're using bServerSide
//In DataTables 1.10 and greater, we can use .fnAjaxReload()
});
this.on("queuecomplete", function() {
uploadProgress = false;
});
}
};
$(window).bind('beforeunload', function () {
if (uploadProgress) {
return sprintf($.i18n._("You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"),
"\n", "\n");
}
});
self.renderImportStatus = function (data, type, full) {
if (typeof data !== "number") {
console.log("Invalid data type for the import_status.");
return;
}
var statusStr = self.IMPORT_STATUS_CODES.UNKNOWN.message;
var importStatusCode = data;
if (self.IMPORT_STATUS_CODES[importStatusCode]) {
statusStr = self.IMPORT_STATUS_CODES[importStatusCode].message;
}
return statusStr;
};
self.renderFileActions = function (data, type, full) {
if (full.import_status == 0) {
return '<a class="deleteFileAction">' + $.i18n._('Delete from Library') + '</a>';
} else if (full.import_status == 1) {
//No actions for pending files
return $.i18n._('N/A');
} else { //Failed downloads
return '<a class="deleteFileAction">' + $.i18n._('Clear') + '</a>';
}
};
$("#recent_uploads_table").on("click", "a.deleteFileAction", function () {
//Grab the file object for the row that was clicked.
// Some tips from the DataTables forums:
// fnGetData is used to get the object behind the row - you can also use
// fnGetPosition if you need to get the index instead
file = $("#recent_uploads_table").dataTable().fnGetData($(this).closest("tr")[0]);
$.ajax({
type: 'DELETE',
url: 'rest/media/' + file.id + "?csrf_token=" + $("#csrf").attr('value'),
success: function (resp) {
self.recentUploadsTable.fnDraw();
},
error: function () {
alert($.i18n._("Error: The file could not be deleted. Please try again later."));
}
});
});
self.setupRecentUploadsTable = function () {
return $("#recent_uploads_table").dataTable({
"bJQueryUI": true,
"bProcessing": false,
"bServerSide": true,
"sAjaxSource": '/plupload/recent-uploads/format/json',
"sAjaxDataProp": 'files',
"bSearchable": false,
"bInfo": true,
//"sScrollY": "200px",
"bFilter": false,
"bSort": false,
//"sDom": '<"H">frtip<"F"l>',
"sDom": '<"dataTables_scrolling"frt><"F"lip>',
"bPaginate": true,
"sPaginationType": "full_numbers",
"oLanguage": getDatatablesStrings({
"sEmptyTable": $.i18n._("No files have been uploaded yet."),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 uploads"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ uploads"),
"sInfoFiltered": $.i18n._("(filtered from _MAX_ total uploads)"),
}),
"aoColumns": [
{"mData": "artist_name", "sTitle": $.i18n._("Creator")},
{"mData": "track_title", "sTitle": $.i18n._("Title")},
{
"mData": "import_status", "sTitle": $.i18n._("Import Status"),
"mRender": self.renderImportStatus
},
{"mData": "utime", "sTitle": $.i18n._("Uploaded")},
{
"mData": "id", "sTitle": $.i18n._("Actions"),
"mRender": self.renderFileActions
}
],
"fnServerData": function (sSource, aoData, fnCallback) {
/* Add some extra data to the sender */
aoData.push({"name": "uploadFilter", "value": self.uploadFilter});
$.getJSON(sSource, aoData, function (json) {
fnCallback(json);
if (json.files) {
var areAnyFileImportsPending = false;
for (var i = 0; i < json.files.length; i++) {
//console.log(file);
var file = json.files[i];
if (file.import_status == 1) {
areAnyFileImportsPending = true;
}
}
if (areAnyFileImportsPending) {
//alert("pending uploads, starting refresh on timer");
self.startRefreshingRecentUploads();
} else if (self.isRecentUploadsRefreshTimerActive) {
self.stopRefreshingRecentUploads();
self.recentUploadsTable.fnDraw();
}
// Update usability hint - in common.js
getUsabilityHint();
}
});
}
});
};
$("#recent_uploads").addTitles("td");
self.isRecentUploadsRefreshTimerActive = false;
self.startRefreshingRecentUploads = function () {
if (!self.isRecentUploadsRefreshTimerActive) { //Prevent multiple timers from running
self.recentUploadsRefreshTimer = setInterval(function() {
self.recentUploadsTable.fnDraw();
}, 3000);
self.isRecentUploadsRefreshTimerActive = true;
}
};
self.stopRefreshingRecentUploads = function () {
clearInterval(self.recentUploadsRefreshTimer);
self.isRecentUploadsRefreshTimerActive = false;
};
$("#upload_status_all").click(function () {
if (self.uploadFilter !== "all") {
self.uploadFilter = "all";
self.recentUploadsTable.fnPageChange(0).fnDraw();
}
});
$("#upload_status_pending").click(function () {
if (self.uploadFilter !== "pending") {
self.uploadFilter = "pending";
self.recentUploadsTable.fnPageChange(0).fnDraw();
}
});
$("#upload_status_failed").click(function () {
if (self.uploadFilter !== "failed") {
self.uploadFilter = "failed";
self.recentUploadsTable.fnPageChange(0).fnDraw();
}
});
//Create the recent uploads table.
self.recentUploadsTable = self.setupRecentUploadsTable();
//$("#recent_uploads_table.div.fg-toolbar").prepend('<b>Custom tool bar! Text/images etc.</b>');
$("#select_type").on("change",function(){
var ttValue = $("#select_type").val();
var ttText = $('#select_type option[value="'+ttValue+'"]').text();
if (ttValue != ""){
$("#upload_type").text(" " + ttText);
$("#upload_type").css("color", "#ff611f");
} else {
$("#upload_type").text(" Tracks");
$("#upload_type").css("color", "#ffffff");
}
Cookies.set('tt_upload', ttValue);
});
});

View file

@ -0,0 +1,849 @@
var AIRTIME = (function (AIRTIME) {
var mod;
if (AIRTIME.podcast === undefined) {
AIRTIME.podcast = {};
}
mod = AIRTIME.podcast;
mod.episodeTables = {};
var endpoint = '/rest/podcast/', PodcastEpisodeTable;
/**
* PodcastController constructor.
*
* @param {angular.scope} $scope angular scope service object
* @param {angular.http} $http angular http service object
* @param {Object} podcast podcast metadata object
* @param {int} podcast.id podcast unique identifier
* @param {string} podcast.title podcast metadata title
* @param {Tab} tab Tab object the controller is being bootstrapped in
*
* @constructor
*/
function PodcastController($scope, $http, podcast, tab) {
// We need to pass in the tab object and the episodes table object so we can reference them
var self = this,
view = tab ? tab.contents : $(document);
//We take a podcast object in as a parameter rather fetching the podcast by ID here because
//when you're creating a new podcast, we already have the object from the result of the POST. We're saving
//a roundtrip by not fetching it again here.
$scope.podcast = podcast;
$scope.tab = tab;
$scope.csrf = jQuery("#csrf").val();
view.find("table").attr("id", "podcast_episodes_" + podcast.id);
self.onSaveCallback = function () {
var successMsg = $('.active-tab .btn-toolbar .success')
successMsg.text($.i18n._("Podcast settings saved")).show("fast");
setTimeout(function () {
successMsg.hide("fast");
}, 5000);
AIRTIME.library.podcastDataTable.fnDraw();
self.$scope.tab.setName(self.$scope.podcast.title);
};
/**
* Save and update the podcast object.
*/
$scope.savePodcast = function () {
$http.put(endpoint + $scope.podcast.id, {csrf_token: $scope.csrf, podcast: $scope.podcast})
.success(function () {
self.onSaveCallback();
});
};
/**
* Generate a smartblock and playlist for this smartblock.
*/
$scope.createSmartblock = function () {
// send smarblock creation instruction to API
$.post(
endpoint + "smartblock",
{
csrf_token: $("#csrf").val(),
id: $scope.podcast.id,
title: $scope.podcast.title
},
function() {
// show success message
var successMsg = $('.active-tab .pc-sb-success')
successMsg.show("fast");
setTimeout(function(){
successMsg.hide("fast");
}, 5000);
// save podcast but do not display notification beside save button below
$http.put(endpoint + $scope.podcast.id,
{
csrf_token: $scope.csrf,
podcast: $scope.podcast
})
.success(function () {
AIRTIME.library.podcastDataTable.fnDraw();
self.$scope.tab.setName(self.$scope.podcast.title);
});
// redraw list of smartblocks just in case they have it visible on the left
dt = $('table[id="library_display"]').dataTable();
dt.fnStandingRedraw();
}
);
};
/**
* Close the tab and discard any changes made to the podcast data.
*/
$scope.discard = function () {
!tab || tab.close();
$scope.podcast = {};
};
self.$scope = $scope;
self.$http = $http;
self.initialize();
return self;
}
/**
* Initialize the controller.
*
* Sets up the internal datatable.
*/
PodcastController.prototype.initialize = function() {
var self = this;
// TODO: this solves a race condition, but we should look for the root cause
AIRTIME.tabs.onResize();
self.$scope.tab.setName(self.$scope.podcast.title);
// Add an onclose hook to the tab to remove the table object and the
// import listener so we don't cause memory leaks.
var podcastId = self.$scope.podcast.id.toString();
self.$scope.tab.assignOnCloseHandler(function () {
if ( AIRTIME.podcast.episodeTables.hasOwnProperty(podcastId) ) {
AIRTIME.podcast.episodeTables[podcastId].destroy();
AIRTIME.podcast.episodeTables[podcastId] = null;
self.$scope.$destroy();
}
});
};
/**
* StationPodcastController constructor.
*
* @param {angular.scope} $scope angular scope service object
* @param {angular.http} $http angular http service object
* @param {Object} podcast podcast metadata object
* @param {int} podcast.id podcast unique identifier
* @param {string} podcast.title podcast metadata title
* @param {Tab} tab Tab object the controller is being bootstrapped in
*
* @constructor
*/
function StationPodcastController($scope, $http, podcast, tab) {
// Super call to parent controller
PodcastController.call(this, $scope, $http, podcast, tab);
this.onSaveCallback = function () {
$http({
method: 'POST',
url: '/preference/station-podcast-settings',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: { stationPodcastPrivacy: $("#podcast-settings").find("input:checked").val() }
}).success(function (data) {
jQuery.extend($scope.podcast, data);
$(".success").text($.i18n._("Podcast settings saved")).slideDown("fast");
setTimeout(function () {
$(".success").slideUp("fast");
}, 2000);
});
};
return this;
}
/**
* Subclass the PodcastController object.
*
* @type {PodcastController}
*/
StationPodcastController.prototype = Object.create(PodcastController.prototype);
/**
* Remove the selected episodes from the station podcast feed.
*/
StationPodcastController.prototype.unpublishSelectedEpisodes = function () {
var self = this, $scope = self.$scope,
episodes = self.episodeTable.getSelectedRows();
jQuery.each(episodes, function () {
self.$http.delete(endpoint + $scope.podcast.id + '/episodes/' + this.id + '?csrf_token=' + $scope.csrf)
.success(function () {
self.reloadEpisodeTable();
});
});
};
/**
* Initialize the Station podcast episode table.
*
* @private
*/
StationPodcastController.prototype._initTable = function() {
var self = this, $scope = this.$scope,
buttons = {
deleteBtn: {
title : $.i18n._('Unpublish'),
iconClass : 'icon-trash',
extraBtnClass : 'btn-danger',
elementId : '',
eventHandlers : {
click: self.unpublishSelectedEpisodes.bind(self)
},
validateConstraints: function () {
return this.getSelectedRows().length >= 1;
}
}
},
params = {
sAjaxSource : endpoint + $scope.podcast.id + '/episodes',
aoColumns: [
// TODO: it might be wrong to use CcFiles here? We should alias this instead
/* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "CcFiles.track_title" , "sClass" : "podcast_episodes_title" , "sWidth" : "170px" },
/* Description */ { "sTitle" : $.i18n._("Description") , "mDataProp" : "CcFiles.description" , "sClass" : "podcast_episodes_description" , "sWidth" : "300px" }
]
};
this.episodeTable = AIRTIME.podcast.initPodcastEpisodeDatatable(
$('.podcast_episodes'),
params,
buttons,
{
hideIngestCheckboxes: true,
podcastId: $scope.podcast.id,
emptyPlaceholder: {
iconClass: "icon-white icon-th-list",
html: $.i18n._("You haven't published any episodes!")
+ "<br/>" + $.i18n._("You can publish your uploaded content from the 'Tracks' view.")
+ "<br/><a target='_parent' href='/showbuilder#tracks'>" + $.i18n._("Try it now") + "</a>"
}
}
);
mod.stationPodcastTable = this.episodeTable.getDatatable();
};
/**
* Initialize the Station podcast.
*/
StationPodcastController.prototype.initialize = function() {
// We want to override the default tab name behaviour and use "My Podcast" for clarity
this._initTable();
};
/**
* @override
*
* Reload the Station podcast episode table.
*/
StationPodcastController.prototype.reloadEpisodeTable = function() {
this.episodeTable.getDatatable().fnDraw();
};
/**
* AngularJS app for podcast functionality.
*
* Bootstrapped for each podcast or Station podcast tab.
*/
mod.podcastApp = angular.module('podcast', [])
.controller('Podcast', ['$scope', '$http', 'podcast', 'tab', PodcastController])
.controller('StationPodcast', ['$scope', '$http', 'podcast', 'tab', StationPodcastController]);
/**
* Implement bulk editing of podcasts in order to accommodate the existing selection
* mechanisms on the frontend.
*
* Bulk methods use a POST request because we need to send data in the request body.
*
* @param selectedData the data to operate on
* @param method HTTP request method type
* @param callback function to run upon success
* @private
*/
function _bulkAction(selectedData, method, callback) {
var ids = [];
selectedData.forEach(function(el) {
var uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+el.id,
t = AIRTIME.tabs.get(uid);
if (t && method == HTTPMethods.DELETE) {
t.close();
}
if (!(t && method == HTTPMethods.GET)) {
ids.push(el.id);
} else if (t != AIRTIME.tabs.getActiveTab()) {
t.switchTo();
}
});
if (ids.length > 0) {
$.post(endpoint + "bulk", {csrf_token: $("#csrf").val(), method: method, ids: ids}, callback);
}
}
/**
* Bootstrap and initialize the Angular app for the podcast being opened.
*
* @param podcast podcast JSON object to pass to the angular app
* @param tab Tab object the angular app will be initialized in
* @private
*/
function _bootstrapAngularApp(podcast, tab) {
mod.podcastApp.value('podcast', podcast);
mod.podcastApp.value('tab', tab);
var wrapper = tab.contents.find(".angular_wrapper");
angular.bootstrap(wrapper.get(0), ["podcast"]);
}
/**
* Initialization function for a podcast tab.
* Called when editing one or more podcasts.
*
* @param data JSON data returned from the server.
* Contains a JSON encoded podcast object and tab
* content HTML and has the following form:
* {
* podcast: '{
* ...
* }'
* html: '<...>'
* }
* @private
*/
function _initAppFromResponse(data) {
var podcast = JSON.parse(data.podcast),
uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+podcast.id,
tab = AIRTIME.tabs.openTab(data.html, uid, null);
_bootstrapAngularApp(podcast, tab);
$(".album_names.help_icon").qtip({
content: {
text: $.i18n._('Overwrite downloaded podcast episodes\' "Album" and "Creator" metadata tag with the Podcast Name specified above and set the track title to the title of the Podcast Episode. This album name can then be used as a search criteria by a smartblock. ')
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
}
});
// Add podcast episode table in right-side panel below podcast edit form
var episodeTable = AIRTIME.podcast.initPodcastEpisodeDatatableWithButtonEvents(
$("#podcast_episodes_" + podcast.id),
podcast.id.toString()
);
episodeTable.reload(podcast.id);
episodeTable.clearSelection()
}
/**
* Initialize the PodcastTable subclass object (from Table).
*
* Do this in its own function to avoid unnecessary reinitialization of the object.
*
* @private
*/
function _initPodcastEpisodeTable() {
PodcastEpisodeTable = function(wrapperDOMNode, bItemSelection, toolbarButtons, dataTablesOptions, config) {
this.config = config; // Internal configuration object
this._setupImportListener();
// Call the superconstructor
return AIRTIME.widgets.Table.call(this, wrapperDOMNode, bItemSelection, toolbarButtons, dataTablesOptions, config.emptyPlaceholder);
}; // Subclass AIRTIME.widgets.Table
PodcastEpisodeTable.prototype = Object.create(AIRTIME.widgets.Table.prototype);
PodcastEpisodeTable.prototype.constructor = PodcastEpisodeTable;
PodcastEpisodeTable.prototype._SELECTORS = Object.freeze({
SELECTION_CHECKBOX: ".airtime_table_checkbox:has(input)",
SELECTION_TABLE_ROW: "tr:has(td.airtime_table_checkbox > input)"
});
/**
* @override
*
* Override the checkbox delegate function in the Table object to change
* the row's checkbox and import status columns depending on the status
* of the episode (unimported: 0, imported: 1, pending import: -1).
*
* @param rowData
* @param callType
* @param dataToSave
*
* @returns {string}
* @private
*/
PodcastEpisodeTable.prototype._datatablesCheckboxDataDelegate = function(rowData, callType, dataToSave) {
var defaultIcon = "<span class='icon icon-circle-arrow-down'></span>",
importIcon = "<span class='sp-checked-icon checked-icon imported-flag'></span>",
pendingIcon = "<span class='loading-icon'></span>";
if (this.config.hideIngestCheckboxes && rowData.ingested && rowData.ingested != 0) {
return rowData.ingested > 0 ? importIcon : pendingIcon;
}
rowData.importIcon = (rowData.ingested != 0) ? (rowData.ingested > 0 ? importIcon : pendingIcon) : defaultIcon;
return AIRTIME.widgets.Table.prototype._datatablesCheckboxDataDelegate.call(this, rowData, callType, dataToSave);
};
/**
* Reload the episode table.
* Since we're sometimes using a static source, define a separate function to fetch and reload the table data.
* We use this when we save the Podcast because we need to flag rows the user is ingesting.
*
* @param [id] optional podcast identifier
*/
PodcastEpisodeTable.prototype.reload = function (id) {
// When using static source data, we instantiate an empty table
// and pass this function the ID of the podcast we want to display.
if (id) this.config.podcastId = id;
var self = this, dt = self._datatable;
dt.block({
message: "",
theme: true,
applyPlatformOpacityRules: false
});
$.get(endpoint + self.config.podcastId + '/episodes', function (json) {
dt.fnClearTable(false);
dt.fnAddData(JSON.parse(json));
}).done(function () {
dt.unblock();
});
};
/**
* Setup an interval that checks for any pending imports and reloads
* the table once imports are finished.
*
* TODO: remember selection; make this more elegant?
*
* @private
*/
PodcastEpisodeTable.prototype._setupImportListener = function () {
var self = this;
self.importListener = setInterval(function () {
var podcastId = self.config.podcastId, pendingRows = [];
if (!podcastId) return false;
var dt = self.getDatatable(), data = dt.fnGetData();
// Iterate over the table data to check for any rows pending import
$.each(data, function () {
if (this.ingested == -1) {
pendingRows.push(this.guid);
}
});
if (pendingRows.length > 0) {
// Manually trigger the Celery task to update the internal
// task reference because the upload will often finish quickly
$.get('/api/poll-celery');
// Fetch the table data if there are pending rows,
// then check if any of the pending rows have
// succeeded or failed before reloading the table.
$.get(endpoint + podcastId + '/episodes', function (json) {
data = JSON.parse(json);
var delta = false;
$.each(data, function () {
var idx = pendingRows.indexOf(this.guid);
if (idx > -1 && this.ingested != -1) {
delta = true;
pendingRows.slice(idx, 0);
}
});
if (delta) { // Has there been a change?
// We already have the data, so there's no reason to call
// reload() here; this also provides a smoother transition
dt.fnClearTable(false);
dt.fnAddData(data);
}
});
}
}, 5000); // Run every 5 seconds
};
/**
* Explicit destructor
*/
PodcastEpisodeTable.prototype.destroy = function () {
clearInterval(this.importListener);
}
}
/**
* Create and show the URL dialog for podcast creation.
*/
mod.createUrlDialog = function () {
$.get('/render/podcast-url-dialog', function(json) {
$(document.body).append(json.html);
$("#podcast_url_dialog").dialog({
title: $.i18n._("Add New Podcast"),
resizable: false,
modal: true,
width: '450px',
height: 129,
close: function () {
$(this).remove();
}
});
});
};
/**
* Find the URL in the podcast creation dialog and POST it to the server
* to store the feed as a Podcast object.
*
* FIXME: we should probably be passing the serialized form into this function instead
*/
mod.addPodcast = function () {
$.post(endpoint, $("#podcast_url_dialog").find("form").serialize(), function(json) {
// Refresh left-side library pane to show newly created podcast
AIRTIME.library.podcastDataTable.fnDraw();
// close modal
$("#podcast_url_dialog").dialog("close");
// open newly created podcast in right-side edit pane
_initAppFromResponse(json);
}).fail(function (e) {
var errors = $("#podcast_url_dialog").find(".errors");
errors.show(200).text(e.responseText);
setTimeout(function () {
errors.hide(200);
}, 3000);
});
};
/**
* Create a bulk request to edit all currently selected podcasts.
*/
mod.editSelectedPodcasts = function () {
_bulkAction(AIRTIME.library.podcastTableWidget.getSelectedRows(), HTTPMethods.GET, function(json) {
json.forEach(function(data) {
_initAppFromResponse(data);
});
});
};
/**
* Create a bulk request to delete all currently selected podcasts.
*/
mod.deleteSelectedPodcasts = function () {
if (confirm($.i18n._("Are you sure you want to delete the selected podcasts from your library?"))) {
_bulkAction(AIRTIME.library.podcastTableWidget.getSelectedRows(), HTTPMethods.DELETE, function () {
AIRTIME.library.podcastDataTable.fnDraw();
});
}
};
/**
* Open metadata editor tabs for each of the selected episodes.
*
* @param {Array} episodes the array of selected episodes
*/
mod.editSelectedEpisodes = function (episodes) {
$.each(episodes, function () {
if (this.file && !Object.keys(this.file).length > 0) return false;
var fileId = this.file_id || this.file.id, uid = AIRTIME.library.MediaTypeStringEnum.FILE + "_" + fileId;
$.get(baseUrl + "library/edit-file-md/id/" + fileId, {format: "json"}, function (json) {
AIRTIME.playlist.fileMdEdit(json, uid);
});
});
};
/**
* Import one or more podcast episodes.
*
* @param {Array} episodes array of episode data to be imported
* @param {PodcastEpisodeTable} dt PodcastEpisode table containing the data
*/
mod.importSelectedEpisodes = function (episodes, dt) {
$.each(episodes, function () {
// remainingDiskSpace is defined in layout.phtml
if (this.enclosure.length > remainingDiskSpace) {
alert("You don't have enough disk space to import " + this.title);
return false;
}
if (this.file && Object.keys(this.file).length > 0) return false;
$.post(endpoint + this.podcast_id + '/episodes', JSON.stringify({
csrf_token: $("#csrf").val(),
episode: this
}), function () {
dt.reload(dt.config.podcastId);
});
remainingDiskSpace -= this.enclosure.length;
});
dt.clearSelection();
};
/**
* Delete one or more podcast episodes.
*
* @param {id:string, type:string}[] data Array of data objects to be deleted
* @param podcastId:string
*/
mod.deleteSelectedEpisodes = function (data, podcastId) {
$.each(data, function () {
AIRTIME.library.fnDeleteItems(data, podcastId);
});
};
/**
* Initialize the podcast episode table with working buttons
*/
mod.initPodcastEpisodeDatatableWithButtonEvents = function (domNode, podcastId) {
/**
* Check the import statuses of each selected episode to see which
* buttons should be enabled or disabled.
*
* @param shouldBeImported whether or not the selected item(s)
* should be imported to obtain a valid result.
*
* @returns {boolean} true if all selected episodes are valid and
* the button should be enabled, otherwise false.
*/
var checkSelectedEpisodeImportStatus = function (shouldBeImported) {
var selected = this.getSelectedRows(), isValid = true;
if (selected.length == 0) return false;
$.each(selected, function () {
if (this.ingested < 0) isValid = false;
var isImported = !$.isEmptyObject(this.file);
if (shouldBeImported ? !isImported : isImported) {
isValid = false;
}
});
return isValid;
};
// Setup the default buttons (new, edit, delete)
podcastEpisodeButtons = AIRTIME.widgets.Table.getStandardToolbarButtons();
$.extend(true, podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.NEW],
{
title: $.i18n._("Import"),
eventHandlers: {
click: function () {
var episodes = mod.episodeTables[podcastId].getSelectedRows();
AIRTIME.podcast.importSelectedEpisodes(episodes, mod.episodeTables[podcastId]);
}
},
validateConstraints: function () {
return checkSelectedEpisodeImportStatus.call(this, false);
}
});
$.extend(true, podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.EDIT],
{
eventHandlers: {
click: function () {
var episodes = mod.episodeTables[podcastId].getSelectedRows();
AIRTIME.podcast.editSelectedEpisodes(episodes);
}
},
validateConstraints: function () {
return checkSelectedEpisodeImportStatus.call(this, true);
}
});
$.extend(true, podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.DELETE],
{
eventHandlers: {
click: function () {
var data = [], episodes = mod.episodeTables[podcastId].getSelectedRows();
$.each(episodes, function () {
data.push({id: this.file.id, type: this.file.ftype});
});
AIRTIME.podcast.deleteSelectedEpisodes(data, podcastId);
}
},
validateConstraints: function () {
return checkSelectedEpisodeImportStatus.call(this, true);
}
});
// Reassign these because integer keys take precedence in iteration order - we want to order based on insertion
// FIXME: this is a pretty flimsy way to try to set up iteration order (possibly not xbrowser compatible?)
podcastEpisodeButtons = {
newBtn : podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.NEW],
editBtn: podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.EDIT],
delBtn : podcastEpisodeButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.DELETE]
};
$.extend(true, podcastEpisodeButtons, {
addToScheduleBtn: {
title : $.i18n._('Add to Schedule'),
iconClass : 'icon-plus',
extraBtnClass : 'btn-small',
elementId : '',
eventHandlers : {
click: function () {
var data = [], selected = AIRTIME.podcast.episodeTables[podcastId].getSelectedRows();
$.each(selected, function () { data.push(this.file); });
AIRTIME.library.addToSchedule(data);
}
},
validateConstraints: function () {
// TODO: change text + behaviour for playlists, smart blocks, etc.
return checkSelectedEpisodeImportStatus.call(this, true);
}
},
viewDescBtn: {
title : $.i18n._("View"),
iconClass : "icon-globe",
extraBtnClass : "btn-small",
elementId : "",
eventHandlers : {
click: function () {
AIRTIME.library.openPodcastEpisodeDialog(podcastId);
}
},
validateConstraints: function () {
return this.getSelectedRows().length == 1;
}
}
});
mod.episodeTables[podcastId] = AIRTIME.podcast.initPodcastEpisodeDatatable(
domNode,
{},
podcastEpisodeButtons,
{
hideIngestCheckboxes: false,
emptyPlaceholder: {
iconClass: "icon-white icon-th-list",
html: $.i18n._("This podcast doesn't have any episodes!")
+ "<br/>" + $.i18n._("Make sure the RSS feed contains audio items (with enclosure tags).")
+ "<br/><a target='_blank' href='http://www.apple.com/ca/itunes/podcasts/specs.html'>" + $.i18n._("Learn about podcasts") + "</a>"
}
}
);
mod.podcastEpisodeDataTable = mod.episodeTables[podcastId].getDatatable();
mod.episodeTables[podcastId].assignDblClickHandler(function () {
var data = mod.podcastEpisodeDataTable.fnGetData(this);
if (!$.isEmptyObject(data.file)) {
mod.dblClickAdd(data.file, data.file.ftype);
} else {
if (data.ingested >= 0) { // Only import if the file isn't pending
AIRTIME.podcast.importSelectedEpisodes([data], mod.episodeTables[podcastId]);
}
}
});
return mod.episodeTables[podcastId];
};
/**
* Initialize the internal datatable for the podcast editor view to hold episode data passed back from the server.
*
* Selection for the internal table represents episodes marked for ingest and is disabled for ingested episodes.
*
* @param {jQuery} domNode the jQuery DOM node to create the table inside.
* @param {Object} buttons JSON object containing datatables button parameters
* @param {Object} config JSON object containing internal PodcastEpisodeTable parameters
* @param {boolean} config.hideIngestCheckboxes flag denoting whether or not to hide checkboxes for ingested items
*
* @returns {Table} the created Table object
*/
mod.initPodcastEpisodeDatatable = function (domNode, params, buttons, config) {
params = $.extend(true, params, {
aoColumns : [
/* GUID */ { "sTitle" : "" , "mDataProp" : "guid" , "sClass" : "podcast_episodes_guid" , "bVisible" : false },
/* Ingested */ { "sTitle" : $.i18n._("Imported?") , "mDataProp" : "importIcon" , "sClass" : "podcast_episodes_imported" , "sWidth" : "120px" },
/* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "title" , "sClass" : "podcast_episodes_title" , "sWidth" : "170px" },
/* Author */ { "sTitle" : $.i18n._("Author") , "mDataProp" : "author" , "sClass" : "podcast_episodes_author" , "sWidth" : "170px" },
/* Description */ { "sTitle" : $.i18n._("Description") , "mDataProp" : "description" , "sClass" : "podcast_episodes_description" , "sWidth" : "300px" },
/* Link */ { "sTitle" : $.i18n._("Link") , "mDataProp" : "link" , "sClass" : "podcast_episodes_link" , "sWidth" : "170px" },
/* Publication Date */ { "sTitle" : $.i18n._("Publication Date") , "mDataProp" : "pub_date" , "sClass" : "podcast_episodes_pub_date" , "sWidth" : "170px" }
],
bServerSide : false,
sAjaxSource : null,
// Initialize the table with empty data so we can defer loading
// If we load sequentially there's a delay before the table appears
aaData : {},
oColVis : {
buttonText: $.i18n._("Columns"),
iOverlayFade: 0,
aiExclude: [0, 1, 2]
},
bDeferRender: true,
oColReorder: {
iFixedColumns: 3 // Checkbox + imported
},
fnCreatedRow: function(nRow, aData, iDataIndex) {
var self = this;
if (aData.file && Object.keys(aData.file).length > 0) {
$(nRow).draggable({
helper: function () {
var $row = $(this), data = self._datatable.fnGetData(nRow);
$row.data("aData", data.file);
self.selectRow(this, data, self.SELECTION_MODE.SINGLE, $row.index());
var selected = self.getSelectedRows().length, container,
width = self._$wrapperDOMNode.closest(".dataTables_wrapper").outerWidth(), message;
message = sprintf($.i18n._(selected > 1 ? "Adding %s Items" : "Adding %s Item"), selected);
container = $('<div/>').attr('id', 'draggingContainer').append('<tr/>')
.find("tr").append('<td/>').find("td")
.attr("colspan", 100).width(width).css("max-width", "none")
.addClass("ui-state-highlight").append(message).end().end();
return container;
},
tolerance: 'pointer',
cursor: 'move',
cursorAt: {
top: 20,
left: Math.floor(self._datatable.outerWidth() / 2)
},
distance: 25, // min-distance for dragging
connectToSortable: $("#show_builder_table, .active-tab .spl_sortable")
});
}
},
fnDrawCallback: function () {
AIRTIME.library.drawEmptyPlaceholder(this);
// Hide the processing div
var dt = this.getDatatable();
!dt || dt.closest(".dataTables_wrapper").find(".dataTables_processing").css("visibility", "hidden");
}
});
if (typeof PodcastEpisodeTable === 'undefined') {
_initPodcastEpisodeTable();
}
var podcastEpisodeTableObj = new PodcastEpisodeTable(
domNode, // DOM node to create the table inside.
true, // Enable item selection
buttons, // Toolbar buttons
params, // Datatables overrides.
config // Internal config
);
podcastEpisodeTableObj.getDatatable().addTitles("td");
return podcastEpisodeTableObj;
};
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(function() {
$(document).on("submit", "#podcast_url_form", function (e) {
e.preventDefault();
AIRTIME.podcast.addPodcast();
});
});

View file

@ -0,0 +1,168 @@
var AIRTIME = (function (AIRTIME) {
var mod;
if (AIRTIME.publish === undefined) {
AIRTIME.publish = {};
}
mod = AIRTIME.publish;
var endpoint = 'rest/media/';
var dialogUrl = 'library/publish-dialog';
var PUBLISH_APP_NAME = 'publish';
//AngularJS app
var publishApp = angular.module(PUBLISH_APP_NAME, [])
.controller('Publish', function ($sce, $scope, $http, mediaId, tab) {
$scope.publishData = {};
var sourceInterval;
tab.contents.on("click", "input[type='checkbox']", function () {
var noSourcesChecked = true;
$.each(tab.contents.find("input[type='checkbox']"), function () {
if ($(this).is(":checked")) {
noSourcesChecked = false;
}
});
tab.contents.find(".publish-btn").prop("disabled", noSourcesChecked);
});
function fetchSourceData() {
var csrfToken = jQuery("#csrf").val();
$http.get(endpoint + mediaId, {csrf_token: csrfToken})
.success(function (json) {
$scope.media = json;
tab.setName($scope.media.track_title);
});
// Get an object containing all sources, their translated labels,
// and their publication state for the file with the given ID
$http.get(endpoint + mediaId + '/publish-sources', {csrf_token: csrfToken})
.success(function (json) {
$scope.sources = { toPublish: [], published: []};
$.each(json, function () {
if (Math.abs(this.status) == 1) {
$scope.sources.published.push(this);
} else {
$scope.sources.toPublish.push(this);
}
});
});
}
function init() {
fetchSourceData();
sourceInterval = setInterval(function() {
fetchSourceData();
}, 5000);
tab.assignOnCloseHandler(function () {
clearInterval(sourceInterval);
$scope.$destroy();
});
}
$scope.openEditDialog = function() {
var uid = AIRTIME.library.MediaTypeStringEnum.FILE + "_" + mediaId;
$.get(baseUrl + "library/edit-file-md/id/" + mediaId, {format: "json"}, function (json) {
AIRTIME.playlist.fileMdEdit(json, uid);
});
};
$scope.publish = function () {
var data = {};
jQuery.each($scope.publishData, function (k, v) {
if (v) {
data[k] = 'publish'; // FIXME: should be more robust
}
});
if (data && Object.keys(data).length > 0) {
$http.put(endpoint + mediaId + '/publish', {csrf_token: jQuery("#csrf").val(), sources: data})
.success(function () {
tab.contents.find(".publish-btn").prop("disabled", true);
fetchSourceData();
$scope.publishData = {}; // Reset the publishData in case the user publishes
// and unpublishes without closing the tab
});
}
};
$scope.remove = function (source) {
var data = {};
data[source] = 'unpublish'; // FIXME: should be more robust
$http.put(endpoint + mediaId + '/publish', {csrf_token: jQuery("#csrf").val(), sources: data})
.success(function () {
fetchSourceData();
});
};
$scope.discard = function () {
tab.close();
$scope.media = {};
};
init();
});
/*
var selected = $("#podcast_table").find(".selected"),
ids = [];
var selectedData = AIRTIME.library.podcastTableWidget.getSelectedRows();
selectedData.forEach(function(el) {
ids.push(el.id);
});*/
function _bootstrapAngularApp(mediaId, tab) {
publishApp.value('mediaId', mediaId);
publishApp.value('tab', tab);
var wrapper = AIRTIME.tabs.getActiveTab().contents.find(".angular_wrapper");
angular.bootstrap(wrapper.get(0), [PUBLISH_APP_NAME]);
}
mod.publishSelectedTracks = function() {
/*
_bulkAction("GET", function(json) {
json.forEach(function(el) {
var uid = AIRTIME.library.MediaTypeStringEnum.FILE+"_"+el.id;
var mediaId = el.id;
$http.get(dialogUrl, { csrf_token: jQuery("#csrf").val() })
.success(function(json) {
AIRTIME.tabs.openTab(json, uid, null);
_bootstrapAngularApp(mediaId);
});
});
});*/
};
mod.openPublishDialog = function(mediaId) {
jQuery.get(dialogUrl, { csrf_token: jQuery("#csrf").val() })
.success(function(html) {
var tab = AIRTIME.tabs.openTab(html, PUBLISH_APP_NAME+"_"+mediaId, null);
_bootstrapAngularApp(mediaId, tab);
});
/*
_bulkAction("GET", function(json) {
json.forEach(function(el) {
var uid = AIRTIME.library.MediaTypeStringEnum.FILE+"_"+el.id;
$http.get(dialogUrl, { csrf_token: jQuery("#csrf").val() })
.success(function(json) {
AIRTIME.tabs.openTab(json, uid, null);
_bootstrapAngularApp(el.media);
});
});
});*/
};
return AIRTIME;
}(AIRTIME || {}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,194 @@
$(document).ready(function() {
listenerstat_content = $("#listenerstat_content")
dateStartId = "#his_date_start",
timeStartId = "#his_time_start",
dateEndId = "#his_date_end",
timeEndId = "#his_time_end";
// set width dynamically
var width = $("#listenerstat_content").width();
width = width * .91;
$("#listenerstat_content").find("#flot_placeholder").width(width);
$("#listenerstat_content").find("#legend").width(width);
getDataAndPlot();
listenerstat_content.find("#his_submit").click(function(){
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
var start = oRange.start;
var end = oRange.end;
getDataAndPlot(start, end);
});
});
/**
* Toggle a spinner overlay so the user knows the page is processing
*/
function toggleOverlay() {
$('#flot_placeholder').toggleClass('processing');
}
function getDataAndPlot(startTimestamp, endTimestamp) {
// Turn on the processing overlay
toggleOverlay();
// get data
$.get(baseUrl+'Listenerstat/get-data', {start: startTimestamp, end: endTimestamp}, function(data){
out = new Object();
$.each(data, function(mpName, v){
plotData = new Object();
plotData.data = new Array();
$.each(v, function(i, ele){
plotData.label = mpName;
var d = new Date(0);
d.setUTCSeconds(ele.timestamp);
plotData.data.push([d, ele.listener_count]);
})
out[mpName] = plotData;
});
plot(out);
// Turn off the processing overlay
toggleOverlay();
})
}
function plot(datasets){
var plot;
data = null;
function plotByChoice(doAll)
{
// largest date object that you can set
firstTimestamp = new Date(8640000000000000);
// smallest
lastTimestamp = new Date(0);
data = [];
if (doAll != null)
{
$.each(datasets, function(key, val) {
if (firstTimestamp.getTime() > val.data[0][0].getTime()) {
firstTimestamp = val.data[0][0];
}
if (lastTimestamp.getTime() < val.data[val.data.length-1][0].getTime()) {
lastTimestamp = val.data[val.data.length-1][0];
}
data.push(val);
});
}
else
{
$('#legend .legendCB').each(
function(){
if (this.checked)
{
data.push(datasets[this.id]);
if (firstTimestamp.getTime() > datasets[this.id].data[0][0].getTime()) {
firstTimestamp = datasets[this.id].data[0][0];
}
if (lastTimestamp.getTime() < datasets[this.id].data[datasets[this.id].data.length-1][0].getTime()) {
lastTimestamp = datasets[this.id].data[datasets[this.id].data.length-1][0];
}
}
else
{
data.push({label: this.id, data: []})
}
}
);
}
numOfTicks = 10;
tickSize = (lastTimestamp.getTime() - firstTimestamp.getTime())/1000/numOfTicks;
plot = $.plot($("#flot_placeholder"), data, {
yaxis: { min: 0, tickDecimals: 0, color: '#d6d6d6', tickColor: '#d6d6d6' },
xaxis: { mode: "time", timeformat:"%y/%m/%0d %H:%M", tickSize: [tickSize, "second"],
color: '#d6d6d6', tickColor: '#d6d6d6' },
grid: {
hoverable: true,
backgroundColor: { colors: ["#333", "#555"] }
},
series: {
lines: {
show: true,
fill: 0.3
},
points: { show: true }
},
legend: {
container: $('#legend'),
noColumns: 5,
color: '#c0c0c0',
labelFormatter: function (label, series) {
var cb = '<input style="float:left;" class="legendCB" type="checkbox" ';
if (series.data.length > 0){
cb += 'checked="true" ';
}
cb += 'id="'+label+'" /> ';
cb += label;
return cb;
}
}
});
function showTooltip(x, y, contents) {
$('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
border: '1px solid #fdd',
padding: '2px',
'background-color': '#fee',
opacity: 0.80
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
$("#flot_placeholder").bind("plothover", function (event, pos, item) {
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
$("#tooltip").remove();
var y = item.datapoint[1].toFixed(2);
showTooltip(item.pageX, item.pageY,
sprintf($.i18n._("Listener Count on %s: %s"), item.series.label, Math.floor(y)));
}
}
else {
$("#tooltip").remove();
previousPoint = null;
}
});
$('#legend').find("input").click(function(){setTimeout(plotByChoice,100);});
}
plotByChoice(true);
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
//i18n_months, i18n_days_short are in common.js
monthNames: i18n_months,
dayNamesMin: i18n_days_short,
onSelect: function(sDate, oDatePicker) {
$(this).datepicker( "setDate", sDate );
}
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
closeButtonText: $.i18n._("Done"),
showLeadingZero: false,
defaultTime: '0:00',
hourText: $.i18n._("Hour"),
minuteText: $.i18n._("Minute")
};
listenerstat_content.find(dateStartId).datepicker(oBaseDatePickerSettings);
listenerstat_content.find(timeStartId).timepicker(oBaseTimePickerSettings);
listenerstat_content.find(dateEndId).datepicker(oBaseDatePickerSettings);
listenerstat_content.find(timeEndId).timepicker(oBaseTimePickerSettings);
}

View file

@ -0,0 +1,127 @@
$(document).ready(function() {
showlistenerstat_content = $("#showlistenerstat_content")
dateStartId = "#his_date_start",
timeStartId = "#his_time_start",
dateEndId = "#his_date_end",
timeEndId = "#his_time_end",
show_id = "#his_show_filter";
// set width dynamically
var width = $("#showlistenerstat_content").width();
width = width * .91;
addDatePicker();
showlistenerstat_content.find("#his_submit").click(function(){
// var show_id = $("#sb_show_filter").val();
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
var start = oRange.start;
var end = oRange.end;
showListenerDataTable();
});
});
function getShowData(startTimestamp, endTimestamp, show_id) {
// get data
$.get(baseUrl+'Listenerstat/get-all-show-data', {start: startTimestamp, end: endTimestamp }, function(data) {
return data;
});
}
function addDatePicker() {
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
//i18n_months, i18n_days_short are in common.js
monthNames: i18n_months,
dayNamesMin: i18n_days_short,
onSelect: function(sDate, oDatePicker) {
$(this).datepicker( "setDate", sDate );
},
onClose: validateTimeRange
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
closeButtonText: $.i18n._("Done"),
showLeadingZero: false,
defaultTime: '0:00',
hourText: $.i18n._("Hour"),
minuteText: $.i18n._("Minute"),
onClose: validateTimeRange
};
showlistenerstat_content.find(dateStartId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange());
showlistenerstat_content.find(timeStartId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange());
showlistenerstat_content.find(dateEndId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange());
showlistenerstat_content.find(timeEndId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange());
}
function getStartEnd() {
return AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
}
function validateTimeRange() {
var oRange,
inputs = $('.date_form > input'),
error_window = $('.error_window'),
start, end;
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
start = oRange.start;
end = oRange.end;
if (end >= start) {
error_window.removeClass('error');
$('.error_window').html('');
}
else {
error_window.addClass('error');
console.log('bad')
$('.error_window').html('Your start date time is after your end date time');
}
return {
start: start,
end: end,
isValid: end >= start
};
}
function showListenerDataTable() {
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
var start = oRange.start;
var lengthMenu = [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, $.i18n._("All")]];
var end = oRange.end;
var sDom = 'l<"dt-process-rel"r><"H"T><"dataTables_scrolling"t><"F"ip>';
var show_id = $("#sb_show_filter").val();
var dt = $('#show_stats_datatable');
info = getStartEnd();
dt.dataTable({
"aoColumns": [
/* first name */ {"sName": "show", "mDataProp": "show"},
/* air date */ {"sName": "time", "mDataProp": "time"},
/* last name */ {"sName": "average_number_of_listeners", "mDataProp": "average_number_of_listeners"},
/* last name */ {"sName": "maximum_number_of_listeners", "mDataProp": "maximum_number_of_listeners"}],
"sAjaxSource": baseUrl+'Listenerstat/get-all-show-data',
"sAjaxDataProp": "",
"bDestroy": true,
"aLengthMenu": lengthMenu,
"iDisplayLength": 25,
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": true,
"sDom": sDom,
"fnServerData": function ( sSource, aoData, fnCallback ) {
aoData.push({"start": start, "end": end});
$.ajax( {
"dataType": 'json',
"type": "POST",
"url": sSource,
"data": {"start": start, "end": end},
"success": fnCallback
} );
},
});
}

View file

@ -0,0 +1,9 @@
$(window).load(function() {
$("#username").focus();
});
$(document).ready(function() {
$("#submit").click(function() {
Cookies.set('airtime_locale', $('#locale').val(), {path: '/'});
});
});

View file

@ -0,0 +1 @@
var viewType = "day";

View file

@ -0,0 +1,51 @@
$(document).ready(function() {
var dialog = $("#lang-timezone-popup");
dialog.dialog({
autoOpen: false,
width: 500,
resizable: false,
modal: true,
closeOnEscape: false,
position:['center','center'],
dialogClass: 'no-close',
buttons: [
/* Testing removing the Not Now button for higher engagement
{
id: "setup-later",
text: $.i18n._("Not Now"),
"class": "btn",
click: function() {
$(this).dialog("close");
}
},*/
{
id: "help_airtime",
text: $.i18n._("OK"),
"class": "btn",
click: function() {
$("#lang-timezone-form").submit();
}
}
]
});
var language = window.navigator.userLanguage || window.navigator.language;
if (language === undefined) {
language = "en_CA";
}
language = language.replace("-", "_");
$("#setup_language").val(language);
dayjs.extend(utc)
dayjs.extend(timezone)
var timezone_name = dayjs.tz.guess();
if (timezone_name === undefined) {
timezone_name = "America/Toronto";
}
$("#setup_timezone").val(timezone_name);
dialog.dialog('open');
});

View file

@ -0,0 +1 @@
var viewType = "now";

View file

@ -0,0 +1,175 @@
$(document).ready(function(){
function doNotShowPopup(){
$.get(baseUrl+"Usersettings/donotshowregistrationpopup", {format:"json"});
}
var dialog = $("#register_popup");
dialog.dialog({
autoOpen: false,
width: 500,
resizable: false,
modal: true,
position:['center',50],
close: doNotShowPopup,
buttons: [
{
id: "remind_me",
text: $.i18n._("Remind me in 1 week"),
"class": "btn",
click: function() {
var url = baseUrl+'Usersettings/remindme';
$.ajax({
url: url,
data: {format:"json"}
});
$(this).dialog("close");
}
},
{
id: "remind_never",
text: $.i18n._("Remind me never"),
"class": "btn",
click: function() {
var url =baseUrl+'Usersettings/remindme-never';
$.ajax({
url: url,
data: {format:"json"}
});
$(this).dialog("close");
}
},
{
id: "help_airtime",
text: sprintf($.i18n._("Yes, help %s"), PRODUCT_NAME),
"class": "btn",
click: function() {
$("#register-form").submit();
}
}
]
});
var button = $("#help_airtime");
if($("#link_to_terms_and_condition").length > 0 ){
button.removeAttr('disabled').removeClass('ui-state-disabled');
}else{
button.attr('disabled', 'disabled' ).addClass('ui-state-disabled');
}
dialog.dialog('open');
$('.collapsible-header').live('click',function() {
$(this).next().toggle('fast');
$(this).toggleClass("close");
return false;
}).next().hide();
$("#SupportFeedback").live('click', function(){
var pub = $("#Publicise");
var privacy = $("#Privacy");
var button = $("#help_airtime");
if( !$(this).is(':checked') ){
pub.removeAttr("checked");
pub.attr("disabled", true);
$("#public-info").hide();
button.attr('disabled', 'disabled' ).addClass('ui-state-disabled');
}else{
pub.removeAttr("disabled");
if(privacy.length == 0 || privacy.is(':checked')){
button.removeAttr('disabled').removeClass('ui-state-disabled');
}
}
});
var promote = $("#Publicise");
promote.live('click', function(){
if($(this).is(':checked')){
$("#public-info").show();
}else{
$("#public-info").hide();
}
});
if( promote.is(":checked")){
$("#public-info").show();
}
$("#Privacy").live('click', function(){
var support = $("#SupportFeedback");
var button = $("#help_airtime");
if($(this).is(':checked') && support.is(':checked')){
button.removeAttr('disabled').removeClass('ui-state-disabled');
}else{
button.attr('disabled', 'disabled' ).addClass('ui-state-disabled');
}
});
if($("#SupportFeedback").is(':checked') && ($("#Privacy").length == 0 || $("#Privacy").is(':checked'))){
button.removeAttr('disabled').removeClass('ui-state-disabled');
}else{
button.attr('disabled', 'disabled' ).addClass('ui-state-disabled');
}
$('.toggle legend').live('click',function() {
$('.toggle').toggleClass('closed');
return false;
});
$("#Logo").live('change', function(ev){
var content, res, logoEl;
content = $(this).val();
res = content.match(/(jpg|jpeg|png|gif)$/gi);
logoEl = $("#Logo-element");
//not an accepted image extension.
if (!res) {
var ul, li;
ul = logoEl.find('.errors');
li = $("<li/>").append($.i18n._("Image must be one of jpg, jpeg, png, or gif"));
//errors ul has already been created.
if (ul.length > 0) {
ul.empty()
.append(li);
}
else {
logoEl
.append('<ul class="errors"></ul>')
.find(".errors")
.append(li);
}
$(this).val("");
}
else {
logoEl.find(".errors").remove();
}
});
});
function resizeImg(ele, targetWidth, targetHeight){
var img = $(ele);
var width = ele.width;
var height = ele.height;
// resize img proportionaly
if( width > height && width > targetWidth){
var ratio = targetWidth/width;
img.css("width", targetHeight+"px");
var newHeight = height * ratio;
img.css("height", newHeight);
}else if( width < height && height > targetHeight){
var ratio = targetHeight/height;
img.css("height", targetHeight+"px");
var newWidth = width * ratio;
img.css("width", newWidth);
}else if( width == height && width > targetWidth){
img.css("height", targetHeight+"px");
img.css("width", targetWidth+"px" );
}
}

View file

@ -0,0 +1,94 @@
function updateEmbedSrcParams()
{
var $embedCodeParams = "?";
var $streamMode = getStreamMode();
if ($streamMode == "manual") {
var $stream = $("input[name=player_stream_url]:radio:checked").val();
$embedCodeParams += "stream="+$stream;
} else if ($streamMode == "auto") {
$embedCodeParams += "stream=auto";
}
$embedCodeParams += "&title="+getPlayerTitle();
$embedCodeParams += "\"";
$("textarea[name=player_embed_src]").val(function(index, value) {
return value.replace(/\?.*?"/, $embedCodeParams);
});
updatePlayerIframeSrc($("textarea[name=player_embed_src]").val());
}
function updatePlayerIframeSrc(iframe_text) {
var $player_iframe = $("#player_form iframe");
var player_iframe_src = iframe_text.match(/http.*?"/)[0].slice(0, -1);
$player_iframe.attr('src', player_iframe_src);
}
function getStreamMode() {
return $("input[name=player_stream_mode]:radio:checked").val();
}
function getPlayerTitle() {
return $("input[name=player_title]").val();
}
$(document).ready(function() {
$("#player_stream_url-element").hide();
// stream mode change event
$("#player_stream_mode-element").change(function() {
var $streamMode = getStreamMode();
if ($streamMode == "auto") {
$("#player_stream_url-element").hide();
} else if ($streamMode == "manual") {
$("#player_stream_url-element").show();
$("input[name=player_stream_url]").each(function(i, obj) {
if ($(this).parent().text().toLowerCase().indexOf("opus") >= 0) {
$(this).attr("disabled", "disabled");
}
});
}
updateEmbedSrcParams();
});
// stream url change event
$("#player_stream_url-element").change(function() {
updateEmbedSrcParams();
});
// display title checkbox change event
$("#player_display_title").change(function() {
if ($(this).prop("checked")) {
$("#player_title-label").show();
$("#player_title-element").show();
} else {
$("#player_title-label").hide();
$("#player_title-element").hide();
}
updateEmbedSrcParams();
});
// title textbox change event
// setup before functions
var typingTimer;
var doneTypingInterval = 3000;
// on keyup, start the countdown
$("input[name=player_title]").keyup(function(){
clearTimeout(typingTimer);
typingTimer = setTimeout(updateEmbedSrcParams, doneTypingInterval);
});
// on keydown, clear the countdown
$("input[name=player_title]").keydown(function(){
clearTimeout(typingTimer);
});
});

View file

@ -0,0 +1,20 @@
function getRandomIdPlayer(max) {
return "playerHtml5Libretime_"+Math.floor(Math.random() * Math.floor(max));
}
function playerhtml5_insert(settings)
{
atp='';
if(settings.autoplay==true) atp='autoplay';
if(settings.forceHTTPS==true&&settings.url.indexOf('https')==-1) settings.url=settings.url.replace(/http/g, 'https');
if(settings.replacePort!=''&&settings.replacePort!=false&&settings.replacePort!='false')
{
if(settings.replacePortTo!='') settings.replacePortTo=':'+settings.replacePortTo;
settings.url=settings.url.replace(':'+settings.replacePort, settings.replacePortTo);
}
if(settings.codec=='mp3') settings.codec='mpeg';
document.getElementById('html5player_skin').innerHTML += '<div id="div_'+settings.elementId+'" style="" ><audio loop controls id="'+settings.elementId+'" src="'+settings.url+'" '+atp+' type="audio/'+settings.codec+'" >'
+'Ihr Browser unterstützt das Element <code>audio</code> nicht.'
+'<\/audio><\/div>';
}

View file

@ -0,0 +1,984 @@
$(document).ready(function() {
setSmartBlockEvents();
});
function setSmartBlockEvents() {
var activeTab = $('.active-tab'),
form = activeTab.find('.smart-block-form');
/********** ADD CRITERIA ROW **********/
form.find('#criteria_add').live('click', function(){
var div = $('dd[id="sp_criteria-element"]').children('div:visible:last');
if (div.length == 0) {
div = $('dd[id="sp_criteria-element"]').children('div:first');
div.children().removeAttr('disabled');
div.show();
appendAddButton();
appendModAddButton();
removeButtonCheck();
disableAndHideDateTimeDropdown(newRowVal);
} else {
div.find('.db-logic-label').text('and').css('display', 'table');
div.removeClass('search-row-or').addClass('search-row-and');
div = div.next().show();
div.children().removeAttr('disabled');
div.find(".modifier_add_link").show();
div = div.next();
if (div.length === 0) {
$(this).hide();
}
appendAddButton();
appendModAddButton();
removeButtonCheck();
// disableAndHideDateTimeDropdown(newRowVal);
groupCriteriaRows();
}
});
/********** ADD MODIFIER ROW **********/
form.find('a[id^="modifier_add"]').live('click', function(){
var criteria_value = $(this).siblings('select[name^="sp_criteria_field"]').val();
//make new modifier row
var newRow = $(this).parent().clone(),
newRowCrit = newRow.find('select[name^="sp_criteria_field"]'),
newRowMod = newRow.find('select[name^="sp_criteria_modifier"]'),
newRowVal = newRow.find('input[name^="sp_criteria_value"]'),
newRowExtra = newRow.find('input[name^="sp_criteria_extra"]'),
newRowRemove = newRow.find('a[id^="criteria_remove"]');
//remove error msg
if (newRow.children().hasClass('errors sp-errors')) {
newRow.find('span[class="errors sp-errors"]').remove();
}
//hide the critieria field select box
newRowCrit.addClass('sp-invisible');
//keep criteria value the same
newRowCrit.val(criteria_value);
//reset all other values
newRowMod.val('0');
newRowVal.val('');
newRowExtra.val('');
disableAndHideExtraField(newRowVal);
disableAndHideDateTimeDropdown(newRowVal);
disableAndHideExtraDateTimeDropdown(newRowVal);
sizeTextBoxes(newRowVal, 'sp_extra_input_text', 'sp_input_text');
//remove the 'criteria add' button from new modifier row
newRow.find('#criteria_add').remove();
$(this).parent().after(newRow);
// remove extra spacing from previous row
newRow.prev().removeClass('search-row-and').addClass('search-row-or');
reindexElements();
appendAddButton();
appendModAddButton();
removeButtonCheck();
groupCriteriaRows();
});
/********** REMOVE ROW **********/
form.find('a[id^="criteria_remove"]').live('click', function(){
var curr = $(this).parent();
var curr_pos = curr.index();
var list = curr.parent();
var list_length = list.find("div:visible").length;
var count = list_length - curr_pos;
var next = curr.next();
var item_to_hide;
var prev;
var index;
//remove error message from current row, if any
var error_element = curr.find('span[class="errors sp-errors"]');
if (error_element.is(':visible')) {
error_element.remove();
}
/* In the case that there is only one element we need to remove the
* date_select drop down.
*/
if (count == 0) {
disableAndHideDateTimeDropdown(curr.find(':first-child'), index);
disableAndHideExtraDateTimeDropdown(curr.find(':first-child'),index);
disableAndHideExtraField(curr.find(':first-child'),index);
}
/* assign next row to current row for all rows below and including
* the row getting removed
*/
for (var i=0; i<count; i++) {
index = getRowIndex(curr);
var criteria = next.find('[name^="sp_criteria_field"]').val();
curr.find('[name^="sp_criteria_field"]').val(criteria);
var modifier = next.find('[name^="sp_criteria_modifier"]').val();
populateModifierSelect(curr.find('[name^="sp_criteria_field"]'), false);
curr.find('[name^="sp_criteria_modifier"]').val(modifier);
var criteria_value = next.find('[name^="sp_criteria_value"]').val();
curr.find('[name^="sp_criteria_value"]').val(criteria_value);
/* if current and next row have the extra criteria value
* (for 'is in the range' modifier), then assign the next
* extra value to current and remove that element from
* next row
*/
if (curr.find('[name^="sp_criteria_extra"]').attr("disabled") != "disabled"
&& next.find('#extra_criteria').is(':visible')) {
var criteria_extra = next.find('[name^="sp_criteria_extra"]').val();
curr.find('[name^="sp_criteria_extra"]').val(criteria_extra);
disableAndHideExtraField(next.find(':first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
} else if (curr.find('[name^="sp_criteria_extra"]').attr("disabled") != "disabled"
&& next.find('#extra_criteria').not(':visible')) {
disableAndHideExtraField(curr.find(':first-child'), index);
/* if only the next row has the extra criteria value,
* then add the extra criteria element to current row
* and assign next row's value to it
*/
} else if (next.find('#extra_criteria').is(':visible')) {
criteria_extra = next.find('[name^="sp_criteria_extra"]').val();
enableAndShowExtraField(curr.find(':first-child'), index);
curr.find('[name^="sp_criteria_extra"]').val(criteria_extra);
}
/* if current and next row have the date_time_select_criteria visible
* then show the current and it from the next row
*/
if (curr.find('[name^="sp_criteria_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#datetime_select').is(':visible')) {
var criteria_datetime = next.find('[name^="sp_criteria_datetime_select"]').val();
curr.find('[name^="sp_criteria_datetime_select"]').val(criteria_datetime);
disableAndHideDateTimeDropdown(next.find('first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
} else if (curr.find('[name^="sp_criteria_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#datetime_select').not(':visible')) {
disableAndHideDateTimeDropdown(curr.find(':first-child'), index);
/* if only the next row has date_time_select then just enable it on the current row
*/
} else if (next.find('#datetime_select').is(':visible')) {
criteria_datetime = next.find('[name^="sp_criteria_datetime_select"]').val();
enableAndShowDateTimeDropdown(curr.find(':first-child'), index);
curr.find('[name^="sp_criteria_datetime_select"]').val(criteria_datetime);
}
/* if current and next row have the extra_date_time_select_criteria visible
* then show the current and it from the next row
*/
if (curr.find('[name^="sp_criteria_extra_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#extra_datetime_select').is(':visible')) {
var extra_criteria_datetime = next.find('[name^="sp_criteria_extra_datetime_select"]').val();
curr.find('[name^="sp_criteria_extra_datetime_select"]').val(extra_criteria_datetime);
disableAndHideExtraDateTimeDropdown(next.find('first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
} else if (curr.find('[name^="sp_criteria_extra_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#extra_datetime_select').not(':visible')) {
disableAndHideExtraDateTimeDropdown(curr.find(':first-child'), index);
/* if only the next row has date_time_select then just enable it on the current row
*/
} else if (next.find('#datetime_select').is(':visible')) {
criteria_datetime = next.find('[name^="sp_criteria_extra_datetime_select"]').val();
enableAndShowExtraDateTimeDropdown(curr.find(':first-child'), index);
curr.find('[name^="sp_criteria_extra_datetime_select"]').val(criteria_datetime);
}
/* determine if current row is a modifier row
* if it is, make the criteria select invisible
*/
prev = curr.prev();
if (curr.find('[name^="sp_criteria_field"]').val() == prev.find('[name^="sp_criteria_field"]').val()) {
if (!curr.find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
curr.find('select[name^="sp_criteria_field"]').addClass('sp-invisible');
}
} else {
if (curr.find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
curr.find('select[name^="sp_criteria_field"]').removeClass('sp-invisible');
}
}
curr = next;
next = curr.next();
}
/* Disable the last visible row since it holds the values the user removed
* Reset the values to empty and resize the criteria value textbox
* in case the row had the extra criteria textbox
*/
item_to_hide = list.find('div:visible:last');
item_to_hide.children().attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_datetime_select"]').attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_extra"]').attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_extra_datetime_select"]').attr('disabled', 'disabled');
if (item_to_hide.find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
item_to_hide.find('select[name^="sp_criteria_field"]').removeClass('sp-invisible');
}
item_to_hide.find('[name^="sp_criteria_field"]').val(0).end()
.find('[name^="sp_criteria_modifier"]').val(0).end()
.find('[name^="sp_criteria_datetime_select"]').end()
.find('[name^="sp_criteria_value"]').val('').end()
.find('[name^="sp_criteria_extra"]').val('')
.find('[name^="sp_criteria_extra_datetime_select"]').end();
sizeTextBoxes(item_to_hide.find('[name^="sp_criteria_value"]'), 'sp_extra_input_text', 'sp_input_text');
item_to_hide.hide();
list.next().show();
//check if last row is a modifier row
var last_row = list.find('div:visible:last');
if (last_row.find('[name^="sp_criteria_field"]').val() == last_row.prev().find('[name^="sp_criteria_field"]').val()) {
if (!last_row.find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
last_row.find('select[name^="sp_criteria_field"]').addClass('sp-invisible');
}
}
// always put '+' button on the last enabled row
appendAddButton();
reindexElements();
// always put '+' button on the last modifier row
appendModAddButton();
// remove the 'x' button if only one row is enabled
removeButtonCheck();
groupCriteriaRows();
});
/********** SAVE ACTION **********/
// moved to spl.js
/********** GENERATE ACTION **********/
activeTab.find('button[id="generate_button"]').live("click", function(){
buttonClickAction('generate', 'playlist/smart-block-generate');
});
/********** SHUFFLE ACTION **********/
activeTab.find('button[id="shuffle_button"]').live("click", function(){
buttonClickAction('shuffle', 'playlist/smart-block-shuffle');
});
/********** CHANGE PLAYLIST TYPE **********/
form.find('dd[id="sp_type-element"]').live("change", function(){
//buttonClickAction('generate', 'playlist/empty-content');
$(".active-tab").find('button[id="save_button"]').click();
setupUI();
AIRTIME.library.checkAddButton();
});
/********** LIMIT CHANGE *************/
form.find('select[id="sp_limit_options"]').live("change", function() {
var limVal = form.find('input[id="sp_limit_value"]');
if ($(this).val() === 'remaining') {
disableAndHideLimitValue();
}
else {
enableAndShowLimitValue();
}
});
/********** CRITERIA CHANGE **********/
form.find('select[id^="sp_criteria"]:not([id^="sp_criteria_modifier"]):not([id^="sp_criteria_datetime"]):not([id^="sp_criteria_extra_datetime"]):not([id^="sp_criteria_value"])').live("change", function(){
var index = getRowIndex($(this).parent());
//need to change the criteria value for any modifier rows
var critVal = $(this).val();
var divs = $(this).parent().nextAll(':visible');
$.each(divs, function(i, div){
var critSelect = $(div).children('select[id^="sp_criteria_field"]');
if (critSelect.hasClass('sp-invisible')) {
critSelect.val(critVal);
/* If the select box is visible we know the modifier rows
* have ended
*/
} else {
return false;
}
});
// disable extra field and hide the span
disableAndHideExtraField($(this), index);
disableAndHideDateTimeDropdown($(this), index);
disableAndHideExtraDateTimeDropdown($(this),index);
if ($( "#sp_criteria_field_" + index +" option:selected" ).val() === 'track_type') {
populateTracktypeSelect(this, false);
} else {
disableAndHideTracktypeDropdown($(this),index);
populateModifierSelect(this, true);
}
});
/********** MODIFIER CHANGE **********/
form.find('select[id^="sp_criteria_modifier"]').live("change", function(){
var criteria_value = $(this).next(),
index_num = getRowIndex($(this).parent());
if ($(this).val().match('before|after')) {
enableAndShowDateTimeDropdown(criteria_value, index_num);
console.log($(this).val());
}
else {
disableAndHideDateTimeDropdown(criteria_value, index_num);
disableAndHideExtraDateTimeDropdown(criteria_value,index_num);
}
if ($(this).val().match('is in the range')) {
enableAndShowExtraField(criteria_value, index_num);
} else {
disableAndHideExtraField(criteria_value, index_num);
}
if ($(this).val().match('between')) {
enableAndShowExtraField(criteria_value, index_num);
enableAndShowDateTimeDropdown(criteria_value,index_num);
enableAndShowExtraDateTimeDropdown(criteria_value,index_num);
}
else {
disableAndHideExtraDateTimeDropdown(criteria_value,index_num);
}
var get_crit_field = $(this).siblings(':first-child');
var crit_field = get_crit_field[0]["id"];
if ($( "#" + crit_field +" option:selected" ).val() === 'track_type') {
if ($(this).val() == "is" || $(this).val() == "is not") {
enableAndShowTracktypeDropdown(criteria_value, index_num);
} else {
disableAndHideTracktypeDropdown(criteria_value, index_num);
}
}
});
setupUI();
appendAddButton();
appendModAddButton();
removeButtonCheck();
}
function getRowIndex(ele) {
var id = ele.find('[name^="sp_criteria_field"]').attr('id'),
delimiter = '_',
start = 3,
tokens = id.split(delimiter).slice(start),
index = tokens.join(delimiter);
return index;
}
/* This function appends a '+' button for the last
* modifier row of each criteria.
* If there are no modifier rows, the '+' button
* remains at the criteria row
*/
function appendModAddButton() {
var divs = $('.active-tab .smart-block-form').find('div select[name^="sp_criteria_modifier"]').parent(':visible');
$.each(divs, function(i, div){
if (i > 0) {
/* If the criteria field is hidden we know it is a modifier row
* and can hide the previous row's modifier add button
*/
if ($(div).find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
$(div).prev().find('a[id^="modifier_add"]').addClass('sp-invisible');
} else {
$(div).prev().find('a[id^="modifier_add"]').removeClass('sp-invisible');
}
}
//always add modifier add button to the last row
if (i+1 == divs.length) {
$(div).find('a[id^="modifier_add"]').removeClass('sp-invisible');
}
});
}
/* This function re-indexes all the form elements.
* We need to do this everytime a row gets deleted
*/
function reindexElements() {
var divs = $('.active-tab .smart-block-form').find('div select[name^="sp_criteria_field"]').parent(),
index = 0,
modIndex = 0;
/* Hide all logic labels
* We will re-add them as each row gets indexed
*/
$('.db-logic-label').text('').hide();
$.each(divs, function(i, div){
if (i > 0 && index < 26) {
/* If the current row's criteria field is hidden we know it is
* a modifier row
*/
if ($(div).find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
if ($(div).is(':visible')) {
$(div).prev().find('.db-logic-label').text($.i18n._("or")).show();
}
modIndex++;
} else {
if ($(div).is(':visible')) {
$(div).prev().find('.db-logic-label').text($.i18n._("and")).show();
}
index++;
modIndex = 0;
}
$(div).find('select[name^="sp_criteria_field"]').attr('name', 'sp_criteria_field_'+index+'_'+modIndex);
$(div).find('select[name^="sp_criteria_field"]').attr('id', 'sp_criteria_field_'+index+'_'+modIndex);
$(div).find('select[name^="sp_criteria_modifier"]').attr('name', 'sp_criteria_modifier_'+index+'_'+modIndex);
$(div).find('select[name^="sp_criteria_modifier"]').attr('id', 'sp_criteria_modifier_'+index+'_'+modIndex);
$(div).find('input[name^="sp_criteria_value"]').attr('name', 'sp_criteria_value_'+index+'_'+modIndex);
$(div).find('input[name^="sp_criteria_value"]').attr('id', 'sp_criteria_value_'+index+'_'+modIndex);
$(div).find('select[name^="sp_criteria_value"]').attr('name', 'sp_criteria_value_'+index+'_'+modIndex);
$(div).find('select[name^="sp_criteria_value"]').attr('id', 'sp_criteria_value_'+index+'_'+modIndex);
$(div).find('input[name^="sp_criteria_extra"]').attr('name', 'sp_criteria_extra_'+index+'_'+modIndex);
$(div).find('input[name^="sp_criteria_extra"]').attr('id', 'sp_criteria_extra_'+index+'_'+modIndex);
$(div).find('a[name^="modifier_add"]').attr('id', 'modifier_add_'+index);
$(div).find('a[id^="criteria_remove"]').attr('id', 'criteria_remove_'+index+'_'+modIndex);
} else if (i > 0) {
$(div).remove();
}
});
}
function buttonClickAction(clickType, url){
var data = $('.active-tab .smart-block-form').serializeArray(),
obj_id = $('.active-tab .obj_id').val();
enableLoadingIcon();
$.post(url, {format: "json", data: data, obj_id: obj_id, obj_type: "block",
modified: AIRTIME.playlist.getModified()
}, function(data){
callback(data, clickType);
disableLoadingIcon();
});
}
function setupUI() {
var activeTab = $('.active-tab'),
playlist_type = activeTab.find('input:radio[name=sp_type]:checked').val();
/* Activate or Deactivate shuffle button
* It is only active if playlist is not empty
*/
var sortable = activeTab.find('.spl_sortable'),
plContents = sortable.children(),
shuffleButton = activeTab.find('button[name="shuffle_button"]'),
generateButton = activeTab.find('button[name="generate_button"]'),
fadesButton = activeTab.find('#spl_crossfade, #pl-bl-clear-content');
if (activeTab.find('#sp_limit_options').val() == 'remaining') {
disableAndHideLimitValue();
}
if (!plContents.hasClass('spl_empty')) {
if (shuffleButton.hasClass('ui-state-disabled')) {
shuffleButton.removeClass('ui-state-disabled');
shuffleButton.removeAttr('disabled');
}
} else if (!shuffleButton.hasClass('ui-state-disabled')) {
shuffleButton.addClass('ui-state-disabled');
shuffleButton.attr('disabled', 'disabled');
}
if (activeTab.find('.obj_type').val() == 'block') {
if (playlist_type == "1") {
shuffleButton.removeAttr("disabled");
generateButton.removeAttr("disabled");
generateButton.html($.i18n._("Generate"));
fadesButton.removeAttr("disabled");
//sortable.children().show();
} else {
shuffleButton.attr("disabled", "disabled");
generateButton.html($.i18n._("Preview"));
fadesButton.attr("disabled", "disabled");
//sortable.children().hide();
}
}
$(".playlist_type_help_icon").qtip({
content: {
text: $.i18n._("A static smart block will save the criteria and generate the block content immediately. This allows you to edit and view it in the Library before adding it to a show.")+"<br /><br />" +
$.i18n._("A dynamic smart block will only save the criteria. The block content will get generated upon adding it to a show. You will not be able to view and edit the content in the Library.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
}
});
$(".repeat_tracks_help_icon").qtip({
content: {
text: sprintf($.i18n._("The desired block length will not be reached if %s cannot find enough unique tracks to match your criteria. Enable this option if you wish to allow tracks to be added multiple times to the smart block."), PRODUCT_NAME)
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
}
});
$(".overflow_tracks_help_icon").qtip({
content: {
text: sprintf($.i18n._("<p>If this option is unchecked, the smartblock will schedule as many tracks as can be played out <strong>in their entirety</strong> within the specified duration. This will usually result in audio playback that is slightly less than the specified duration.</p><p>If this option is checked, the smartblock will also schedule one final track which will overflow the specified duration. This final track may be cut off mid-way if the show into which the smartblock is added finishes.</p>"), PRODUCT_NAME)
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
}
});
activeTab.find('.collapsible-header').off('click').on('click', function(){
$(this).toggleClass('visible');
$('.smart-block-advanced').toggle();
});
}
function enableAndShowTracktypeDropdown(valEle, index) {
console.log('tracktype show');
$("#sp_criteria_value_"+index).replaceWith('<select name="sp_criteria_value_'+index+'" id="sp_criteria_value_'+index+'" class="input_select sp_input_select"></select>');
$.each(stringTracktypeOptions, function(key, value){
$("#sp_criteria_value_"+index).append($('<option></option>').attr('value', key).text(value));
});
}
function disableAndHideTracktypeDropdown(valEle, index) {
console.log('tracktype hide');
$("#sp_criteria_value_"+index).replaceWith('<input type="text" name="sp_criteria_value_'+index+'" id="sp_criteria_value_'+index+'" value="" class="input_text sp_input_text">');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and shows the criteria drop-down
*/
function enableAndShowDateTimeDropdown(valEle, index) {
console.log('datetime show');
var spanDatetime = valEle.nextAll("#datetime_select");
spanDatetime.children('#sp_criteria_datetime_select_'+index).removeAttr("disabled");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).removeAttr("disabled");
spanDatetime.show();
//make value input smaller since we have extra element now
var criteria_val = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_val, 'sp_input_text', 'sp_extra_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and hides the datetime criteria drop-down
*/
function disableAndHideDateTimeDropdown(valEle, index) {
console.log('datetime hide');
var spanDatetime = valEle.nextAll("#datetime_select");
spanDatetime.children('#sp_criteria_datetime_select_'+index).val("").attr("disabled", "disabled");
spanDatetime.hide();
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_value, 'sp_extra_input_text', 'sp_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and shows the criteria drop-down
*/
function enableAndShowExtraDateTimeDropdown(valEle, index) {
console.log('datetime show');
var spanDatetime = valEle.nextAll("#extra_datetime_select");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).removeAttr("disabled");
spanDatetime.show();
//make value input smaller since we have extra element now
var criteria_val = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_val, 'sp_input_text', 'sp_extra_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and hides the datetime criteria drop-down
*/
function disableAndHideExtraDateTimeDropdown(valEle, index) {
console.log('datetime hide');
var spanDatetime = valEle.nextAll("#extra_datetime_select");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).val("").attr("disabled", "disabled");
spanDatetime.hide();
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_value, 'sp_extra_input_text', 'sp_input_text');
}
function enableAndShowExtraField(valEle, index) {
var spanExtra = valEle.nextAll("#extra_criteria");
console.log('shown');
spanExtra.children('#sp_criteria_extra_'+index).removeAttr("disabled");
spanExtra.show();
//make value input smaller since we have extra element now
var criteria_val = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_val, 'sp_input_text', 'sp_extra_input_text');
}
function disableAndHideExtraField(valEle, index) {
var spanExtra = valEle.nextAll("#extra_criteria");
spanExtra.children('#sp_criteria_extra_'+index).val("").attr("disabled", "disabled");
spanExtra.hide();
console.log('hidden');
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_value, 'sp_extra_input_text', 'sp_input_text');
}
function disableAndHideLimitValue() {
console.log('we hide it');
$('#sp_limit_value').hide();
}
function enableAndShowLimitValue() {
console.log('we show it');
$('#sp_limit_value').show();
}
function sizeTextBoxes(ele, classToRemove, classToAdd) {
if (ele.hasClass(classToRemove)) {
ele.removeClass(classToRemove).addClass(classToAdd);
}
}
function populateModifierSelect(e, popAllMods) {
var criteria_type = getCriteriaOptionType(e),
index = getRowIndex($(e).parent()),
divs;
if (popAllMods) {
index = index.substring(0, 1);
}
divs = $(e).parents().find('select[id^="sp_criteria_modifier_'+index+'"]');
$.each(divs, function(i, div){
$(div).children().remove();
if (criteria_type == 's') {
$.each(stringCriteriaOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
}
else if(criteria_type == 'd') {
$.each(dateTimeCriteriaOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
}
else if(criteria_type == 'tt') {
$.each(stringIsNotOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
}
else {
$.each(numericCriteriaOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
}
});
}
function populateTracktypeSelect(e, popAllMods) {
var criteria_type = getTracktype(e),
index = getRowIndex($(e).parent()),
divs;
if (popAllMods) {
index = index.substring(0, 1);
}
divs = $(e).parents().find('select[id^="sp_criteria_modifier_'+index+'"]');
$.each(divs, function(i, div){
$(div).children().remove();
$.each(stringIsNotOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
});
}
function getCriteriaOptionType(e) {
var criteria = $(e).val();
return criteriaTypes[criteria];
}
function getTracktype(e) {
var type = $(e).val();
return stringTracktypeOptions[type];
}
function callback(json, type) {
var dt = $('table[id="library_display"]').dataTable(),
form = $('.active-tab .smart-block-form');
if (json.modified !== undefined) {
AIRTIME.playlist.setModified(json.modified);
}
if (type == 'shuffle' || type == 'generate') {
if (json.error !== undefined) {
alert(json.error);
}
if (json.result == "0") {
if (type == 'shuffle') {
form.find('.success').text($.i18n._('Smart block shuffled'));
} else if (type == 'generate') {
form.find('.success').text($.i18n._('Smart block generated and criteria saved'));
//redraw library table so the length gets updated
dt.fnStandingRedraw();
}
AIRTIME.playlist.playlistResponse(json);
form.find('.success').show();
}
removeButtonCheck();
form.find('.smart-block-form').removeClass("closed");
} else {
if (json.result == "0") {
$('.active-tab #sp-success-saved').text($.i18n._('Smart block saved')).show();
AIRTIME.playlist.playlistResponse(json);
//redraw library table so the length gets updated
dt.fnStandingRedraw();
}
else {
AIRTIME.playlist.playlistResponse(json);
removeButtonCheck();
}
form.find('.smart-block-form').removeClass("closed");
}
setTimeout(removeSuccessMsg, 5000);
}
function appendAddButton() {
/*
var add_button = "<a class='btn btn-small' id='criteria_add'>" +
"<i class='icon-white icon-plus'></i>Add Criteria</a>";
var rows = $('.active-tab .smart-block-form'),
enabled = rows.find('select[name^="sp_criteria_field"]:enabled');
rows.find('#criteria_add').remove();
if (enabled.length > 1) {
rows.find('select[name^="sp_criteria_field"]:enabled:last')
.siblings('a[id^="criteria_remove"]')
.after(add_button);
} else {
enabled.siblings('span[id="extra_criteria"]')
.after(add_button);
}
*/
}
function removeButtonCheck() {
/*
var rows = $('.active-tab dd[id="sp_criteria-element"]').children('div'),
enabled = rows.find('select[name^="sp_criteria_field"]:enabled'),
rmv_button = enabled.siblings('a[id^="criteria_remove"]');
if (enabled.length == 1) {
rmv_button.attr('disabled', 'disabled');
rmv_button.hide();
} else {
rmv_button.removeAttr('disabled');
rmv_button.show();
}*/
}
function enableLoadingIcon() {
// Disable the default overlay style
$.blockUI.defaults.overlayCSS = {};
$(".side_playlist.active-tab").block({
//message: $.i18n._("Processing..."),
message: $.i18n._(""),
theme: true,
allowBodyStretch: true,
applyPlatformOpacityRules: false
});
}
function disableLoadingIcon() {
$(".side_playlist.active-tab").unblock()
}
function groupCriteriaRows() {
// check whether rows should be "grouped" and shown with an "or" "logic label", or separated by an "and" "logic label"
var visibleRows = $("#sp_criteria-element > div:visible"),
prevRowGroup = "0";
visibleRows.each(function (index){
if (index > 0) {
var fieldId = $(this).find('select[id^="sp_criteria_field"]').attr("id");
var currRowGroup = fieldId[fieldId.length - 3];
if (currRowGroup === prevRowGroup) {
$(this).prev().addClass("search-row-or").removeClass("search-row-and")
} else {
$(this).prev().addClass("search-row-and").removeClass("search-row-or")
}
prevRowGroup = currRowGroup;
}
});
// ensure spacing below last visible row
$("#sp_criteria-element > div:visible:last").addClass("search-row-and").removeClass("search-row-or");
}
// We need to know if the criteria value will be a string
// or numeric value in order to populate the modifier
// select list
var criteriaTypes = {
0 : "",
"album_title" : "s",
"bit_rate" : "n",
"bpm" : "n",
"composer" : "s",
"conductor" : "s",
"copyright" : "s",
"cuein" : "n",
"cueout" : "n",
"description" : "s",
"artist_name" : "s",
"encoded_by" : "s",
"utime" : "d",
"mtime" : "d",
"lptime" : "d",
"genre" : "s",
"isrc_number" : "s",
"label" : "s",
"language" : "s",
"length" : "n",
"mime" : "s",
"mood" : "s",
"owner_id" : "s",
"replay_gain" : "n",
"sample_rate" : "n",
"track_title" : "s",
"track_number" : "n",
"info_url" : "s",
"year" : "n",
"track_type" : "tt"
};
var stringCriteriaOptions = {
"0" : $.i18n._("Select modifier"),
"contains" : $.i18n._("contains"),
"does not contain" : $.i18n._("does not contain"),
"is" : $.i18n._("is"),
"is not" : $.i18n._("is not"),
"starts with" : $.i18n._("starts with"),
"ends with" : $.i18n._("ends with")
};
var numericCriteriaOptions = {
"0" : $.i18n._("Select modifier"),
"is" : $.i18n._("is"),
"is not" : $.i18n._("is not"),
"is greater than" : $.i18n._("is greater than"),
"is less than" : $.i18n._("is less than"),
"is in the range" : $.i18n._("is in the range")
};
var dateTimeCriteriaOptions = {
"0" : $.i18n._("Select modifier"),
"before" : $.i18n._("before"),
"after" : $.i18n._("after"),
"between" : $.i18n._("between"),
"is" : $.i18n._("is"),
"is not" : $.i18n._("is not"),
"is greater than" : $.i18n._("is greater than"),
"is less than" : $.i18n._("is less than"),
"is in the range" : $.i18n._("is in the range")
};
var stringIsNotOptions = {
"0" : $.i18n._("Select modifier"),
"is" : $.i18n._("is"),
"is not" : $.i18n._("is not")
};
let tracktypes = TRACKTYPES;
var stringTracktypeOptions = Object.assign({"": "Select Track Type"}, tracktypes);

View file

@ -0,0 +1,167 @@
var AIRTIME = (function(AIRTIME) {
var mod;
var $templateDiv;
var $templateList;
var $fileMDList;
if (AIRTIME.itemTemplate === undefined) {
AIRTIME.itemTemplate = {};
}
mod = AIRTIME.itemTemplate;
//config: name, type, filemd, required
function createTemplateLi(config) {
var templateRequired =
"<li " +
"data-name='<%= name %>' " +
"data-type='<%= type %>' " +
"data-filemd='<%= filemd %>'" +
"data-label='<%= label %>'" +
"class='<%= (filemd) ? 'field_filemd' : 'field_other' %>'" +
">" +
"<span><%= label %></span>" +
"<span><%= type %></span>" +
"</li>";
var templateOptional =
"<li " +
"data-name='<%= name %>' " +
"data-type='<%= type %>' " +
"data-filemd='<%= filemd %>'" +
"data-label='<%= label %>'" +
"class='<%= (filemd) ? 'field_filemd' : 'field_other' %>'" +
">" +
"<span><%= label %></span>" +
"<span><%= type %></span>" +
"<span class='template_item_remove'><i class='icon icon-trash'></i></span>" +
"</li>";
var template = (config.required) === true ? templateRequired : templateOptional;
template = _.template(template);
var $li = $(template(config));
return $li;
}
//taken from
//http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
function randomString(len, charSet) {
//can only use small letters to avoid DB query problems.
charSet = charSet || 'abcdefghijklmnopqrstuvwxyz';
var randomString = '';
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomPoz,randomPoz+1);
}
return randomString;
}
function addField(config) {
$templateList.append(createTemplateLi(config));
}
function getFieldData($el) {
return {
name: $el.data("name"),
type: $el.data("type"),
label: $el.data("label"),
isFileMd: $el.data("filemd")
};
}
mod.onReady = function() {
$templateDiv = $("#configure_item_template");
$templateList = $(".template_item_list");
$fileMDList = $(".template_file_md");
$fileMDList.on("click", "i.icon-plus", function(){
var $li = $(this).parents("li");
var config = {
name: $li.data("name"),
type: $li.data("type"),
label: $li.data("label"),
filemd: true,
required: false
};
addField(config);
$li.remove();
});
$templateList.sortable();
$templateDiv.on("click", ".template_item_remove", function() {
$(this).parents("li").remove();
});
$templateDiv.on("click", ".template_item_add button", function() {
var $div = $(this).parents("div.template_item_add"),
$input = $div.find("input"),
label = $input.val(),
name;
$input.val("");
//create a string name that will work for all languages.
name = randomString(10);
var config = {
name: name,
label: label,
type: $div.find("select").val(),
filemd: false,
required: false
};
addField(config);
});
function updateTemplate(template_id, isDefault) {
var url = baseUrl+"Playouthistorytemplate/update-template/format/json";
var data = {};
var $lis, $li;
var i, len;
var templateName;
templateName = $("#template_name").val();
$lis = $templateList.children();
for (i = 0, len = $lis.length; i < len; i++) {
$li = $($lis[i]);
data[i] = getFieldData($li);
}
$.post(url, {'id': template_id, 'name': templateName, 'fields': data, 'setDefault': isDefault}, function(json) {
var x;
});
}
$templateDiv.on("click", "#template_item_save", function(){
var template_id = $(this).data("template");
updateTemplate(template_id, false);
});
$templateDiv.on("click", "#template_set_default", function() {
var $btn = $(this),
template_id = $btn.data("template"),
url = baseUrl+"Playouthistorytemplate/set-template-default/format/json";
$btn.remove();
$.post(url, {id: template_id});
});
};
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(AIRTIME.itemTemplate.onReady);

View file

@ -0,0 +1,948 @@
var AIRTIME = (function(AIRTIME) {
var mod;
if (AIRTIME.history === undefined) {
AIRTIME.history = {};
}
mod = AIRTIME.history;
var $historyContentDiv;
var lengthMenu = [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, $.i18n._("All")]];
var sDom = 'l<"dt-process-rel"r><"H"><"dataTables_scrolling"t><"F"ip>';
var selectedLogItems = {};
var dateStartId = "#his_date_start",
timeStartId = "#his_time_start",
dateEndId = "#his_date_end",
timeEndId = "#his_time_end",
oTableAgg,
oTableItem,
oTableShow,
inShowsTab = false;
function validateTimeRange() {
var oRange,
inputs = $('.his-timerange > input'),
start, end;
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
start = oRange.start;
end = oRange.end;
if (end >= start) {
inputs.removeClass('error');
}
else {
inputs.addClass('error');
}
return {
start: start,
end: end,
isValid: end >= start
};
}
function getSelectedLogItems() {
var items = Object.keys(selectedLogItems);
return items;
}
function addSelectedLogItem($el) {
var id;
$el.addClass("his-selected");
id = $el.data("his-id");
selectedLogItems[id] = "";
}
function removeSelectedLogItem($el) {
var id;
$el.removeClass("his-selected");
id = $el.data("his-id");
delete selectedLogItems[id];
}
function emptySelectedLogItems() {
var $inputs = $historyContentDiv.find(".his_checkbox").find("input");
$inputs.prop('checked', false);
$inputs.parents("tr").removeClass("his-selected");
selectedLogItems = {};
}
function selectCurrentPage(e) {
var $ctx = $(e.currentTarget).parents("div.dataTables_wrapper"),
$inputs = $ctx.find(".his_checkbox").find("input"),
$tr,
$input;
$.each($inputs, function(index, input) {
$input = $(input);
$input.prop('checked', true);
$tr = $input.parents("tr");
addSelectedLogItem($tr);
});
}
function deselectCurrentPage(e) {
var $ctx = $(e.currentTarget).parents("div.dataTables_wrapper"),
$inputs = $ctx.find(".his_checkbox").find("input"),
$tr,
$input;
$.each($inputs, function(index, input) {
$input = $(input);
$input.prop('checked', false);
$tr = $input.parents("tr");
removeSelectedLogItem($tr);
});
}
function getFileName(ext){
var filename = $("#his_date_start").val()+"_"+$("#his_time_start").val()+"m--"+$("#his_date_end").val()+"_"+$("#his_time_end").val()+"m";
filename = filename.replace(/:/g,"h");
if (ext == "pdf"){
filename = filename+".pdf";
}
else {
filename = filename+".csv";
}
return filename;
}
/* This callback can be used for all history tables */
function fnServerData( sSource, aoData, fnCallback ) {
if (fnServerData.hasOwnProperty("start")) {
aoData.push( { name: "start", value: fnServerData.start} );
}
if (fnServerData.hasOwnProperty("end")) {
aoData.push( { name: "end", value: fnServerData.end} );
}
if (fnServerData.hasOwnProperty("instance")) {
aoData.push( { name: "instance_id", value: fnServerData.instance} );
}
aoData.push( { name: "format", value: "json"} );
$.ajax( {
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
}
function createShowAccordSection(config) {
var template,
$el;
template =
"<h3>" +
"<a href='#'>" +
"<span class='show-title'><%= name %></span>" +
"<span class='push-right'>" +
"<span class='show-date'><%= date %></span>" +
"<span class='show-time'><%= startTime %></span>" +
"-" +
"<span class='show-time'><%= endTime %></span>" +
"</span>" +
"</a>" +
"</h3>" +
"<div " +
"data-instance='<%= instance %>' " +
"></div>";
template = _.template(template);
$el = $(template(config));
return $el;
}
//$el is the div in the accordian we should create the table on.
function createShowTable($el) {
var instance = $el.data("instance");
var $table = $("<table/>", {
'cellpadding': "0",
'cellspacing': "0",
'class': "datatable",
'id': "history_table_show"
});
//assign the retrieval function the show instance id.
fnServerData.instance = instance;
$el.append($table);
$el.css("height", "auto");
oTableShow = itemHistoryTable("history_table_show");
}
function drawShowList(oShows) {
var $showList = $historyContentDiv.find("#history_show_summary"),
i,
len,
$accordSection,
show,
tmp;
$showList
.accordion( "destroy" )
.empty();
for (i = 0, len = oShows.length; i < len; i++) {
show = oShows[i];
tmp = show.starts.split(" ");
$accordSection = createShowAccordSection({
instance: show.instance_id,
name: show.name,
date: tmp[0],
startTime: tmp[1],
endTime: show.ends.split(" ").pop()
});
$showList.append($accordSection);
}
$showList.accordion({
animated: false,
create: function( event, ui ) {
var $div = $showList.find(".ui-accordion-content-active");
console.log(event);
//$div.css()
createShowTable($div);
},
change: function( event, ui ) {
var $div = $(ui.newContent);
$(ui.oldContent).empty();
createShowTable($div);
selectedLogItems = {};
}
//changestart: function( event, ui ) {}
});
}
function createToolbarButtons ($el) {
var $menu = $("<div class='btn-toolbar' />");
$menu.append("<div class='btn-group'>" +
"<button class='btn btn-small btn-new' id='his_create'>" +
"<i class='icon-white icon-plus'></i>" +
$.i18n._("New Log Entry") +
"</button>" +
"</div>");
$menu.append("<div class='btn-group'>" +
"<button class='btn btn-small dropdown-toggle' data-toggle='dropdown'>" +
$.i18n._("Select")+" <span class='caret'></span>" +
"</button>" +
"<ul class='dropdown-menu'>" +
"<li class='his-select-page'><a href='#'>"+$.i18n._("Select this page")+"</a></li>" +
"<li class='his-dselect-page'><a href='#'>"+$.i18n._("Deselect this page")+"</a></li>" +
"<li class='his-dselect-all'><a href='#'>"+$.i18n._("Deselect all")+"</a></li>" +
"</ul>" +
"</div>");
$menu.append("<div class='btn-group'>" +
"<button class='btn btn-small dropdown-toggle' data-toggle='dropdown'>" +
$.i18n._("Export")+" <span class='caret'></span>" +
"</button>" +
"<ul class='dropdown-menu'>" +
"<li id='csv_export'><a href='#'>"+$.i18n._("Export as CSV")+"</a></li>" +
"<li id='pdf_export'><a href='#'>"+$.i18n._("Export as PDF")+"</a></li>" +
"</ul>" +
"</div>");
$menu.append("<div class='btn-group'>" +
"<button class='btn btn-small' id='his_trash'>" +
"<i class='icon-white icon-trash'></i>" +
"</button>" +
"</div>");
$el.append($menu);
}
function aggregateHistoryTable() {
var oTable,
$historyTableDiv = $historyContentDiv.find("#history_table_aggregate"),
columns,
fnRowCallback;
fnRowCallback = function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var editUrl = baseUrl+"playouthistory/edit-file-item/id/"+aData.file_id,
$nRow = $(nRow);
$nRow.data('url-edit', editUrl);
};
columns = JSON.parse(localStorage.getItem('datatables-historyfile-aoColumns'));
oTable = $historyTableDiv.dataTable( {
"aoColumns": columns,
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": baseUrl+"playouthistory/file-history-feed",
"sAjaxDataProp": "history",
"fnServerData": fnServerData,
"fnRowCallback": fnRowCallback,
"oLanguage": getDatatablesStrings({
"sEmptyTable": $.i18n._("No tracks were played during the selected time period."),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 tracks"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ tracks"),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 tracks"),
"sInfoFiltered": $.i18n._("(filtered from _MAX_ total tracks)"),
}),
"aLengthMenu": lengthMenu,
"iDisplayLength": 25,
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": true,
"sDom": sDom,
});
oTable.fnSetFilteringDelay(350);
return oTable;
}
function itemHistoryTable(id) {
var oTable,
$historyTableDiv = $historyContentDiv.find("#"+id),
$toolbar,
columns,
fnRowCallback,
booleans = {},
i, c;
columns = JSON.parse(localStorage.getItem('datatables-historyitem-aoColumns'));
for (i in columns) {
c = columns[i];
if (c["sDataType"] === "boolean") {
booleans[c["mDataProp"]] = c["sTitle"];
}
}
fnRowCallback = function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
var editUrl = baseUrl+"playouthistory/edit-list-item/id/"+aData.history_id,
deleteUrl = baseUrl+"playouthistory/delete-list-item/id/"+aData.history_id,
emptyCheckBox = String.fromCharCode(parseInt(2610, 16)),
checkedCheckBox = String.fromCharCode(parseInt(2612, 16)),
b,
text,
$nRow = $(nRow);
// add checkbox
$nRow.find('td.his_checkbox').html("<input type='checkbox' name='cb_"+aData.history_id+"'>");
$nRow.data('his-id', aData.history_id);
$nRow.data('url-edit', editUrl);
$nRow.data('url-delete', deleteUrl);
for (b in booleans) {
text = aData[b] ? checkedCheckBox : emptyCheckBox;
text = text + " " + booleans[b];
$nRow.find(".his_"+b).html(text);
}
};
oTable = $historyTableDiv.dataTable( {
"aoColumns": columns,
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": baseUrl+"playouthistory/item-history-feed",
"sAjaxDataProp": "history",
"fnServerData": fnServerData,
"fnRowCallback": fnRowCallback,
"oLanguage": getDatatablesStrings({
"sEmptyTable": $.i18n._("No tracks were played during the selected time period."),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 tracks"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ tracks"),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 tracks"),
"sInfoFiltered": $.i18n._("(filtered from _MAX_ total tracks)"),
}),
"aLengthMenu": lengthMenu,
"iDisplayLength": 25,
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": true,
"sDom": sDom,
});
oTable.fnSetFilteringDelay(350);
$toolbar = $historyTableDiv.parents(".dataTables_wrapper").find(".fg-toolbar:first");
createToolbarButtons($toolbar);
return oTable;
}
function showSummaryList(start, end) {
var url = baseUrl+"playouthistory/show-history-feed",
data = {
format: "json",
start: start,
end: end
};
$.post(url, data, function(json) {
drawShowList(json);
});
}
mod.onReady = function() {
var oBaseDatePickerSettings,
oBaseTimePickerSettings,
$hisDialogEl,
tabsInit = [
{
initialized: false,
initialize: function() {
oTableItem = itemHistoryTable("history_table_list");
},
navigate: function() {
delete fnServerData.instance;
oTableItem.fnDraw();
},
always: function() {
inShowsTab = false;
emptySelectedLogItems();
}
},
{
initialized: false,
initialize: function() {
oTableAgg = aggregateHistoryTable();
},
navigate: function() {
delete fnServerData.instance;
oTableAgg.fnDraw();
},
always: function() {
inShowsTab = false;
emptySelectedLogItems();
}
},
{
initialized: false,
initialize: function() {
},
navigate: function() {
},
always: function() {
inShowsTab = true;
var info = getStartEnd();
showSummaryList(info.start, info.end);
emptySelectedLogItems();
}
}
];
//set the locale names for the bootstrap calendar.
$.fn.datetimepicker.dates = {
daysMin: i18n_days_short,
months: i18n_months,
monthsShort: i18n_months_short
};
$historyContentDiv = $("#history_content");
function redrawTables() {
oTableAgg && oTableAgg.fnDraw();
oTableItem && oTableItem.fnDraw();
oTableShow && oTableShow.fnDraw();
}
function removeHistoryDialog() {
$hisDialogEl.dialog("destroy");
$hisDialogEl.remove();
}
function initializeDialog() {
var $startPicker = $hisDialogEl.find('#his_item_starts_datetimepicker'),
$endPicker = $hisDialogEl.find('#his_item_ends_datetimepicker');
$startPicker.datetimepicker();
$endPicker.datetimepicker({
showTimeFirst: true
});
$startPicker.on('changeDate', function(e) {
$endPicker.data('datetimepicker').setLocalDate(e.localDate);
});
}
function processDialogHtml($el) {
if (inShowsTab) {
$el.find("#his_choose_instance").remove();
}
return $el
}
function makeHistoryDialog(html) {
$hisDialogEl = $(html);
$hisDialogEl = processDialogHtml($hisDialogEl);
$hisDialogEl.dialog({
title: $.i18n._("Edit History Record"),
modal: false,
open: function( event, ui ) {
initializeDialog();
},
close: function() {
removeHistoryDialog();
}
});
}
hisSubmit(); // Fixes display bug
/*
* Icon hover states for search.
*/
$historyContentDiv.on("mouseenter", ".his-timerange .ui-button", function(ev) {
$(this).addClass("ui-state-hover");
});
$historyContentDiv.on("mouseleave", ".his-timerange .ui-button", function(ev) {
$(this).removeClass("ui-state-hover");
});
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
//i18n_months, i18n_days_short are in common.js
monthNames: i18n_months,
dayNamesMin: i18n_days_short,
onSelect: function(sDate, oDatePicker) {
$(this).datepicker( "setDate", sDate );
},
onClose: validateTimeRange
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
closeButtonText: $.i18n._("Done"),
showLeadingZero: false,
defaultTime: '0:00',
hourText: $.i18n._("Hour"),
minuteText: $.i18n._("Minute"),
onClose: validateTimeRange
};
$historyContentDiv.find(dateStartId)
.datepicker(oBaseDatePickerSettings)
.blur(validateTimeRange);
$historyContentDiv.find(timeStartId)
.timepicker(oBaseTimePickerSettings)
.blur(validateTimeRange);
$historyContentDiv.find(dateEndId)
.datepicker(oBaseDatePickerSettings)
.blur(validateTimeRange);
$historyContentDiv.find(timeEndId)
.timepicker(oBaseTimePickerSettings)
.blur(validateTimeRange);
$historyContentDiv.on("click", "#his_create", function(e) {
var url = baseUrl+"playouthistory/edit-list-item/format/json" ;
e.preventDefault();
$.get(url, function(json) {
makeHistoryDialog(json.dialog);
}, "json");
});
$historyContentDiv.on("click", "#pdf_export", async function(){
// Get date/time from pickers
var startDay = document.querySelector('#his_date_start').value;
var startTime = document.querySelector('#his_time_start').value;
var endDay = document.querySelector('#his_date_end').value;
var endTime = document.querySelector('#his_time_end').value;
var url = baseUrl + "api/item-history-feed?start=" + startDay + " " + startTime + "&end=" + endDay + " " + endTime;
var requestData = await fetch(url);
var hisData = await requestData.json();
if (!hisData.length) {
alert("The date range selected doesn't have any items to export.");
return
} else {
// Generate PDF template
var dd = {
content: [
{text: 'Libretime', style: 'subheader'},
{text: 'Playout History from ' + startDay + ' ' + startTime + ' to ' + endDay + ' ' + endTime, style: 'header'},
{
style: 'mainTable',
table: {
headerRows: 1,
body: [
[{text: 'Start Time', style: 'tableHeader'}, {text: 'End Time', style: 'tableHeader'}, {text: 'Song', style: 'tableHeader'}, {text: 'Artist', style: 'tableHeader'}],
]
}
}
],
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 0, 0, 10]
},
subheader: {
fontSize: 14,
bold: true,
margin: [0, 10, 0, 5]
},
mainTable: {
margin: [0, 5, 0, 15]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
}
},
defaultStyle: {
}
};
hisData.forEach(element => {
// Removing extra fields
delete element.checkbox;
delete element.history_id;
delete element.instance_id;
dd.content[2].table.body.push(Object.values(element));
});
// Make PDF and start download
pdfMake.createPdf(dd).download();
};
});
$historyContentDiv.on("click", "#csv_export", async function(){
// Get date/time from pickers
var startDay = document.querySelector('#his_date_start').value;
var startTime = document.querySelector('#his_time_start').value;
var endDay = document.querySelector('#his_date_end').value;
var endTime = document.querySelector('#his_time_end').value;
var url = baseUrl + "api/item-history-feed?start=" + startDay + " " + startTime + "&end=" + endDay + " " + endTime;
var requestData = await fetch(url);
var hisData = await requestData.json();
if (!hisData.length) {
alert("The date range selected doesn't have any items to export.");
return
} else {
// Clean returned data
hisData.forEach(element => {
// Start date/time
element.startDate = element.starts.split(" ")[0];
element.startTime = element.starts.split(" ")[1];
// End date/time
element.endDate = element.ends.split(" ")[0];
element.endTime = element.ends.split(" ")[1];
// Moving Title and Artist fields to the end
element.title = element.track_title;
element.artist = element.artist_name;
// Removing extra fields
delete element.checkbox;
delete element.history_id;
delete element.instance_id;
delete element.starts; // we already converted these, so we don't need them anymore
delete element.ends;
delete element.track_title;
delete element.artist_name;
});
};
var csvX = new CSVExport(hisData); // Actual export function
return false // Was part of the demo. Please leave as is.
});
$('body').on("click", ".his_file_cancel, .his_item_cancel", function(e) {
removeHistoryDialog();
});
$('body').on("click", ".his_file_save", function(e) {
e.preventDefault();
var $form = $(this).parents("form");
var data = $form.serializeArray();
var url = baseUrl+"Playouthistory/update-file-item/format/json";
$.post(url, data, function(json) {
//TODO put errors on form.
if (json.error !== undefined) {
//makeHistoryDialog(json.dialog);
}
else {
removeHistoryDialog();
redrawTables();
}
}, "json");
});
$('body').on("click", ".his_item_save", function(e) {
e.preventDefault();
var $form = $(this).parents("form"),
data = $form.serializeArray(),
id = data[0].value,
createUrl = baseUrl+"Playouthistory/create-list-item/format/json",
updateUrl = baseUrl+"Playouthistory/update-list-item/format/json",
url,
$select = $hisDialogEl.find("#his_instance_select"),
instance;
url = (id === "") ? createUrl : updateUrl;
if (fnServerData.instance !== undefined) {
data.push({
name: "instance_id",
value: fnServerData.instance
});
}
else if ($select.length > 0) {
instance = $select.val();
if (instance > 0) {
data.push({
name: "instance_id",
value: instance
});
}
}
$.post(url, data, function(json) {
if (json.form !== undefined) {
var $newForm = $(json.form);
$newForm = processDialogHtml($newForm);
$hisDialogEl.html($newForm.html());
initializeDialog();
}
else {
removeHistoryDialog();
redrawTables();
}
}, "json");
});
$historyContentDiv.on("click", ".his_checkbox input", function(e) {
var checked = e.currentTarget.checked,
$tr = $(e.currentTarget).parents("tr");
if (checked) {
addSelectedLogItem($tr);
}
else {
removeSelectedLogItem($tr);
}
});
$('body').on("click", "#his_instance_retrieve", function(e) {
var startPicker = $hisDialogEl.find('#his_item_starts'),
endPicker = $hisDialogEl.find('#his_item_ends'),
url = baseUrl+"playouthistory/show-history-feed",
startDate = startPicker.val(),
endDate = endPicker.val(),
data;
data = {
start: startDate,
end: endDate,
format: "json"
};
$.get(url, data, function(json) {
var i,
$select = $('<select/>', {
id: 'his_instance_select'
}),
$option,
show;
if (json.length > 0) {
for (i = 0; i < json.length; i++) {
show = json[i];
$option = $('<option/>')
.text(show.name)
.attr('value', show.instance_id);
$select.append($option);
}
}
$option = $('<option/>')
.text($.i18n._("No Show"))
.attr('value', 0);
$select.append($option);
$hisDialogEl.find("#his_instance_select").replaceWith($select);
});
});
function getStartEnd() {
return AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
}
function hisSubmit(){
var fn, info;
info = getStartEnd();
fn = fnServerData;
fn.start = info.start;
fn.end = info.end;
if (inShowsTab) {
showSummaryList(info.start, info.end);
}
else {
redrawTables();
};
};
$historyContentDiv.find("#his_submit").click(function(ev){
hisSubmit();
});
$historyContentDiv.on("click", ".his-select-page", selectCurrentPage);
$historyContentDiv.on("click", ".his-dselect-page", deselectCurrentPage);
$historyContentDiv.on("click", ".his-dselect-all", emptySelectedLogItems);
$historyContentDiv.on("click", "#his_trash", function(ev){
var items = getSelectedLogItems(),
url = baseUrl+"playouthistory/delete-list-items";
$.post(url, {ids: items, format: "json"}, function() {
selectedLogItems = {};
redrawTables();
});
});
$historyContentDiv.find("#his-tabs").tabs({
show: function( event, ui ) {
var href = $(ui.tab).attr("href");
var index = href.split('-').pop();
var tab = tabsInit[index-1];
if (!tab.initialized) {
tab.initialize();
tab.initialized = true;
}
else {
tab.navigate();
}
tab.always();
}
});
// begin context menu initialization.
$.contextMenu({
selector: '#history_content td:not(.his_checkbox)',
trigger: "left",
ignoreRightClick: true,
build: function($el, e) {
var items = {},
callback,
$tr,
editUrl,
deleteUrl;
$tr = $el.parents("tr");
editUrl = $tr.data("url-edit");
deleteUrl = $tr.data("url-delete");
if (editUrl !== undefined) {
callback = function() {
$.post(editUrl, {format: "json"}, function(json) {
makeHistoryDialog(json.dialog);
}, "json");
};
items["edit"] = {
"name": $.i18n._("Edit"),
"icon": "edit",
"callback": callback
};
}
if (deleteUrl !== undefined) {
callback = function() {
var c = confirm("Delete this entry?");
if (c) {
$.post(deleteUrl, {format: "json"}, function(json) {
redrawTables();
});
}
};
items["del"] = {
"name": $.i18n._("Delete"),
"icon": "delete",
"callback": callback
};
}
return {
items: items
};
}
});
};
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(AIRTIME.history.onReady);

View file

@ -0,0 +1,102 @@
var AIRTIME = (function(AIRTIME) {
var mod;
var $historyTemplate;
if (AIRTIME.template === undefined) {
AIRTIME.template = {};
}
mod = AIRTIME.template;
function createItemLi(id, name, configured) {
var editUrl = baseUrl+"Playouthistorytemplate/configure-template/id/"+id;
var defaultUrl = baseUrl+"Playouthistorytemplate/set-template-default/format/json/id/"+id;
var removeUrl = baseUrl+"Playouthistorytemplate/delete-template/format/json/id/"+id;
var itemConfigured =
"<li class='template_configured' data-template='<%= id %>' data-name='<%= name %>'>" +
"<a href='<%= editUrl %>' class='template_name'><%= name %></a>" +
"<i class='icon icon-ok'></i>" +
"</li>";
var item =
"<li data-template='<%= id %>' data-name='<%= name %>'>" +
"<a href='<%= editUrl %>' class='template_name'><%= name %></a>" +
"<a href='<%= removeUrl %>' class='template_remove'><i class='icon icon-trash'></i></a>" +
"<a href='<%= defaultUrl %>' class='template_default'>" + $.i18n._('Set Default') + "</a>" +
"</li>";
var template = (configured) === true ? itemConfigured : item;
var template = _.template(template);
var $li = $(template({id: id, name: name, editUrl: editUrl, defaultUrl: defaultUrl, removeUrl: removeUrl}));
return $li;
}
mod.onReady = function() {
$historyTemplate = $("#history_template");
$historyTemplate.on("click", ".template_remove", function(ev) {
ev.preventDefault();
var $a = $(this);
var url = $a.attr("href");
$a.parents("li").remove();
$.post(url, function(){
var x;
});
});
$historyTemplate.on("click", ".template_default", function(ev) {
ev.preventDefault();
var $a = $(this);
var url = $a.attr("href");
var $oldLi, $newLi;
$oldLi = $a.parents("ul").find("li.template_configured");
$newLi = $a.parents("li");
$oldLi.replaceWith(createItemLi($oldLi.data('template'), $oldLi.data('name'), false));
$newLi.replaceWith(createItemLi($newLi.data('template'), $newLi.data('name'), true));
$.post(url, function(){
var x;
});
});
function createTemplate(type) {
var createUrl = baseUrl+"Playouthistorytemplate/create-template";
$.post(createUrl, {format: "json", type: type}, function(json) {
if (json.error !== undefined) {
alert(json.error);
return;
}
window.location.href = json.url;
});
}
$historyTemplate.on("click", "#new_item_template", function() {
createTemplate("item");
});
$historyTemplate.on("click", "#new_file_template", function() {
createTemplate("file");
});
};
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(AIRTIME.template.onReady);

View file

@ -0,0 +1,129 @@
function setWatchedDirEvents() {
$('#storageFolder-selection').serverBrowser({
onSelect: function(path) {
$('#storageFolder').val(path);
},
onLoad: function() {
return $('#storageFolder').val();
},
width: 500,
height: 250,
position: ['center', 'center'],
//knownPaths: [{text:'Desktop', image:'desktop.png', path:'/home'}],
knownPaths: [],
imageUrl: 'img/icons/',
systemImageUrl: baseUrl+'css/img/',
handlerUrl: baseUrl+'Preference/server-browse/format/json',
title: $.i18n._('Choose Storage Folder'),
basePath: '',
requestMethod: 'POST',
});
$('#watchedFolder-selection').serverBrowser({
onSelect: function(path) {
$('#watchedFolder').val(path);
},
onLoad: function() {
return $('#watchedFolder').val();
},
width: 500,
height: 250,
position: ['center', 'center'],
//knownPaths: [{text:'Desktop', image:'desktop.png', path:'/home'}],
knownPaths: [],
imageUrl: 'img/icons/',
systemImageUrl: baseUrl+'css/img/',
handlerUrl: baseUrl+'Preference/server-browse/format/json',
title: $.i18n._('Choose Folder to Watch'),
basePath: '',
requestMethod: 'POST',
});
$('#storageFolder-ok').click(function(){
var url, chosen;
if(confirm(sprintf($.i18n._("Are you sure you want to change the storage folder?\nThis will remove the files from your %s library!"), PRODUCT_NAME))){
url = baseUrl+"Preference/change-stor-directory";
chosen = $('#storageFolder').val();
$.post(url,
{format: "json", dir: chosen, element: "storageFolder"},
function(json) {
$("#watched-folder-section").empty();
$("#watched-folder-section").append(json.subform);
setWatchedDirEvents();
});
}
else {
$('#storageFolder').val("");
}
});
$('#watchedFolder-ok').click(function(){
var url, chosen;
url = baseUrl+"Preference/reload-watch-directory";
chosen = $('#watchedFolder').val();
$.post(url,
{format: "json", dir: chosen, element: "watchedFolder"},
function(json) {
$("#watched-folder-section").empty();
$("#watched-folder-section").append("<h2>"+$.i18n._("Manage Media Folders")+"</h2>");
$("#watched-folder-section").append(json.subform);
setWatchedDirEvents();
});
});
$('.selected-item').find('.ui-icon-refresh').click(function(){
var folder = $(this).prev().text();
$.get(baseUrl+"Preference/rescan-watch-directory", {format: "json", dir: folder});
});
$('.selected-item').find('.ui-icon-close').click(function(){
if(confirm($.i18n._("Are you sure you want to remove the watched folder?"))){
var row = $(this).parent();
var folder = row.find('#folderPath').text();
url = baseUrl+"Preference/remove-watch-directory";
$.post(url,
{format: "json", dir: folder},
function(json) {
$("#watched-folder-section").empty();
$("#watched-folder-section").append("<h2>"+$.i18n._("Manage Media Folders")+"</h2>");
$("#watched-folder-section").append(json.subform);
setWatchedDirEvents();
});
}
});
}
$(document).ready(function() {
setWatchedDirEvents();
$(".ui-icon-alert").qtip({
content: {
text: $.i18n._("This path is currently not accessible.")
},
position:{
adjust: {
resize: true,
method: "flip flip"
},
at: "right center",
my: "left top",
viewport: $(window)
},
style: {
classes: "ui-tooltip-dark"
},
show: 'mouseover',
hide: 'mouseout'
});
});

View file

@ -0,0 +1,197 @@
function showErrorSections() {
var selector = $("[id$=-settings]");
selector.each(function(i) {
var el = $(this);
var errors = el.find(".errors");
if (errors.length > 0) {
el.show();
$(window).scrollTop(errors.position().top);
}
});
}
function setConfigureMailServerListener() {
var configMailServer = $("#configureMailServer");
configMailServer.click(function(event){
setMailServerInputReadonly();
});
var msRequiresAuth = $("#msRequiresAuth");
msRequiresAuth.click(function(event){
setMsAuthenticationFieldsReadonly($(this));
});
}
function setEnableSystemEmailsListener() {
var enableSystemEmails = $("#enableSystemEmail");
enableSystemEmails.click(function(event){
setSystemFromEmailReadonly();
});
}
function setPodcastAutoSmartblockReadonly() {
var disablePodcastAutomSmartblock = $("#podcastAutoSmartblock-0");
var enablePodcastAutomSmartblock = $("#podcastAutoSmartblock-1");
var podcastOverride = $("#podcastAlbumOverride-1");
if ($(podcastOverride).is(':checked')) {
enablePodcastAutomSmartblock.removeAttr("readonly");
} else {
disablePodcastAutomSmartblock.prop("checked", true);
disablePodcastAutomSmartblock.attr("readonly","readonly");
enablePodcastAutomSmartblock.attr("readonly","readonly");
}
}
function setSystemFromEmailReadonly() {
var enableSystemEmails = $("#enableSystemEmail");
var systemFromEmail = $("#systemEmail");
if ($(enableSystemEmails).is(':checked')) {
systemFromEmail.removeAttr("readonly");
} else {
systemFromEmail.attr("readonly", "readonly");
}
}
function setMailServerInputReadonly() {
var configMailServer = $("#configureMailServer");
var mailServer = $("#mailServer");
var port = $("#port");
var requiresAuthCB = $("#msRequiresAuth");
if (configMailServer.is(':checked')) {
mailServer.removeAttr("readonly");
port.removeAttr("readonly");
requiresAuthCB.parent().show();
} else {
mailServer.attr("readonly", "readonly");
port.attr("readonly", "readonly");
requiresAuthCB.parent().hide();
}
setMsAuthenticationFieldsReadonly(requiresAuthCB);
}
function setTuneInSettingsListener() {
var enableTunein = $("#enable_tunein");
enableTunein.click(function(event){
setTuneInSettingsReadonly();
});
}
function setTuneInSettingsReadonly() {
var enableTunein = $("#enable_tunein");
var stationId = $("#tunein_station_id");
var partnerKey = $("#tunein_partner_key");
var partnerId = $("#tunein_partner_id");
if (enableTunein.is(':checked')) {
stationId.removeAttr("readonly");
partnerKey.removeAttr("readonly");
partnerId.removeAttr("readonly");
} else {
stationId.attr("readonly", "readonly");
partnerKey.attr("readonly", "readonly");
partnerId.attr("readonly", "readonly");
}
}
/*
* Enable/disable mail server authentication fields
*/
function setMsAuthenticationFieldsReadonly(ele) {
var email = $("#email");
var password = $("#ms_password");
var configureMailServer = $("#configureMailServer");
if (ele.is(':checked') && configureMailServer.is(':checked')) {
email.removeAttr("readonly");
password.removeAttr("readonly");
} else if (ele.not(':checked') || configureMailServer.not(':checked')) {
email.attr("readonly", "readonly");
password.attr("readonly", "readonly");
}
}
function removeLogo() {
$.post(baseUrl+'preference/remove-logo', {'csrf_token' : $('#csrf').val()}, function(json){
// Reload without resubmitting the form
location.href = location.href.replace(location.hash,"");
});
}
function deleteAllFiles() {
var resp = confirm($.i18n._("Are you sure you want to delete all the tracks in your library?"))
if (resp) {
$.post(baseUrl+'preference/delete-all-files', {'csrf_token' : $('#csrf').val()}, function(json){
location.reload();
});
}
}
$(document).ready(function() {
$('.collapsible-header').live('click',function() {
$(this).next().toggle('fast');
$(this).toggleClass("closed");
return false;
});
if ($("#tunein-settings").find(".errors").length > 0) {
$(".collapsible-content#tunein-settings").show();
}
/* No longer using AJAX for this form. Zend + our code makes it needlessly hard to deal with. -- Albert
$('#pref_save').live('click', function() {
var data = $('#pref_form').serialize();
var url = baseUrl+'Preference/index';
$.post(url, {format: "json", data: data}, function(json){
$('#content').empty().append(json.html);
setTimeout(removeSuccessMsg, 5000);
showErrorSections();
setMailServerInputReadonly();
setConfigureMailServerListener();
setEnableSystemEmailsListener();
});
});*/
// when an image is uploaded, preview it to the user
var logo = $("#stationLogo"),
preview = $("#logo-img");
logo.change(function(e) {
if (this.files && this.files[0]) {
preview.show();
var reader = new FileReader(); // browser compatibility?
reader.onload = function (e) {
console.log("Reader loaded");
preview.attr('src', e.target.result);
};
// check image size so we don't crash the page trying to render
if (validateImage(this.files[0], logo)) {
// read the image data as though it were a data URI
reader.readAsDataURL(this.files[0]);
} else {
// remove the file element data
$(this).val('').replaceWith($(this).clone(true));
preview.hide();
}
} else {
preview.hide();
}
});
if (preview.attr('src').indexOf('images/') > -1) {
$("#logo-remove-btn").hide();
}
showErrorSections();
setMailServerInputReadonly();
setPodcastAutoSmartblockReadonly();
setSystemFromEmailReadonly();
setConfigureMailServerListener();
setEnableSystemEmailsListener();
setTuneInSettingsReadonly();
setTuneInSettingsListener();
});

View file

@ -0,0 +1,499 @@
function showErrorSections() {
$(".errors").each(function(i){
if($(this).length > 0){
var div = $(this).closest("div")
if(div.attr('class') == "stream-setting-content"){
$(this).closest("div").show();
$(this).closest("fieldset").removeClass('closed');
$(window).scrollTop($(this).closest("div").position().top);
}
}
});
}
function rebuildStreamURL(ele){
var div = ele.closest("div")
host = div.find("input[id$=-host]").val()
port = div.find("input[id$=-port]").val()
mount = div.find("input[id$=-mount]").val()
streamurl = ""
if(div.find("select[id$=-output]").val()=="icecast"){
streamurl = "http://"+host
if($.trim(port) != ""){
streamurl += ":"+port
}
if($.trim(mount) != ""){
streamurl += "/"+mount
}
}else{
streamurl = "http://"+host+":"+port+"/"
}
div.find("#stream_url").html('<a href="' + streamurl + '" target="_blank">' + streamurl + '</a>')
}
function restrictOggBitrate(ele, on){
var div = ele.closest("div")
if(on){
if(parseInt(div.find("select[id$=data-bitrate]").val(),10) < 48){
div.find("select[id$=data-bitrate]").find("option[value='48']").attr("selected","selected");
}
div.find("select[id$=data-bitrate]").find("option[value='24']").attr("disabled","disabled");
div.find("select[id$=data-bitrate]").find("option[value='32']").attr("disabled","disabled");
}else{
div.find("select[id$=data-bitrate]").find("option[value='24']").removeAttr("disabled");
div.find("select[id$=data-bitrate]").find("option[value='32']").removeAttr("disabled");
}
}
function hideForShoutcast(ele){
var div = ele.closest("div")
div.find("#outputMountpoint-label").hide()
div.find("#outputMountpoint-element").hide()
div.find("#outputUser-label").hide()
div.find("#outputUser-element").hide()
div.find("select[id$=data-type]").find("option[value='mp3']").attr('selected','selected');
div.find("select[id$=data-type]").find("option[value='ogg']").attr("disabled","disabled");
div.find("select[id$=data-type]").find("option[value='opus']").attr("disabled","disabled");
restrictOggBitrate(ele, false)
}
function validate(ele,evt) {
var theEvent = evt || window.event;
var key = theEvent.keyCode || theEvent.which;
if ((ele.val().length >= 5 || (key < 48 || key > 57)) && !(key == 8 || key == 9 || key == 13 || key == 37 || key == 39 || key == 46)) {
theEvent.returnValue = false;
if(theEvent.preventDefault) theEvent.preventDefault();
}
}
function showForIcecast(ele){
var div = ele.closest("div");
div.find("#outputMountpoint-label").show();
div.find("#outputMountpoint-element").show();
div.find("#outputUser-label").show();
div.find("#outputUser-element").show();
div.find("select[id$=data-type]").find("option[value='ogg']").removeAttr("disabled");
div.find("select[id$=data-type]").find("option[value='opus']").removeAttr("disabled");
}
function checkLiquidsoapStatus(){
var url = baseUrl+'Preference/get-liquidsoap-status/format/json';
var id = $(this).attr("id");
$.post(url, function(json_obj){
for(var i=0;i<json_obj.length;i++){
var obj = json_obj[i];
var id;
var status;
for(var key in obj){
if(key == "id"){
id = obj[key];
}
if(key == "status"){
status = obj[key];
}
}
var html;
if(status == "OK"){
html = '<div class="stream-status status-good"><p>'+$.i18n._("Connected to the streaming server")+'</p></div>';
}else if(status == "N/A"){
html = '<div class="stream-status status-disabled"><p>'+$.i18n._("The stream is disabled")+'</p></div>';
}else if(status == "waiting"){
html = '<div class="stream-status status-info"><p>'+$.i18n._("Getting information from the server...")+'</p></div>';
}else{
html = '<div class="stream-status status-error"><p>'+$.i18n._("Can not connect to the streaming server")+'</p><p>'+status+'</p></div>';
}
$("#s"+id+"Liquidsoap-error-msg-element").html(html);
}
setTimeout(checkLiquidsoapStatus, 2000);
});
}
function setLiveSourceConnectionOverrideListener(){
$("[id=connection_url_override]").click(function(event){
var url_input = $(this).parent().find("dd[id$='_source_host-element']").children();
url_input.removeAttr("readonly");
$(this).parent().find("div[id$='_dj_connection_url_actions']").show();
event.preventDefault();
});
// set action for "OK" and "X"
var live_dj_actions = $("#live_dj_connection_url_actions");
var live_dj_input = live_dj_actions.parent().find("dd[id$='_source_host-element']").children();
var master_dj_actions = $("#master_dj_connection_url_actions");
var master_dj_input = master_dj_actions.parent().find("dd[id$='_source_host-element']").children();
live_dj_actions.find("#ok").click(function(event){
event.preventDefault();
var url = live_dj_input.val();
live_dj_input.val(url);
live_dj_input.attr("readonly", "readonly");
live_dj_actions.hide();
$.get(baseUrl+"Preference/set-source-connection-url", {format: "json", type: "livedj", url:encodeURIComponent(url), override: 1});
event.preventDefault();
});
live_dj_actions.find("#reset").click(function(event){
event.preventDefault();
var port = $("#show_source_port").val();
var mount = $("#show_source_mount").val();
if (mount.charAt(0) != '/') {
mount = ('/').concat(mount);
}
var url = "http://"+location.hostname+":"+port+mount;
live_dj_input.val(url);
live_dj_input.attr("readonly", "readonly");
live_dj_actions.hide();
$.get(baseUrl+"Preference/set-source-connection-url", {format: "json", type: "livedj", url:encodeURIComponent(url), override: 0});
event.preventDefault();
});
master_dj_actions.find("#ok").click(function(event){
var url = master_dj_input.val();
master_dj_input.val(url);
master_dj_input.attr("readonly", "readonly");
master_dj_actions.hide();
$.get(baseUrl+"Preference/set-source-connection-url", {format: "json", type: "masterdj", url:encodeURIComponent(url), override: 1});
event.preventDefault();
});
master_dj_actions.find("#reset").click(function(event){
var port = $("#master_source_port").val();
var mount = $("#master_source_mount").val();
if (mount.charAt(0) != '/') {
mount = ('/').concat(mount);
}
var url = "http://"+location.hostname+":"+port+mount;
master_dj_input.val(url);
master_dj_input.attr("readonly", "readonly");
master_dj_actions.hide();
$.get(baseUrl+"Preference/set-source-connection-url", {format: "json", type: "masterdj", url:encodeURIComponent(url), override: 0});
event.preventDefault();
});
}
function setupEventListeners() {
// initial stream url
$("dd[id=outputStreamURL-element]").each(function(){
rebuildStreamURL($(this));
})
$("input[id$=-host], input[id$=-port], input[id$=-mount]").keyup(function(){
rebuildStreamURL($(this));
});
$("input[id$=-port]").keypress(function(e){
validate($(this),e);
});
$("select[id$=-output]").change(function(){
rebuildStreamURL($(this));
});
if(!$("#output_sound_device").is(':checked')){
$("select[id=output_sound_device_type]").attr('disabled', 'disabled');
}else{
$("select[id=output_sound_device_type]").removeAttr('disabled');
}
$("#output_sound_device").change(function(){
if($(this).is(':checked')){
$("select[id=output_sound_device_type]").removeAttr('disabled');
}else{
$("select[id=output_sound_device_type]").attr('disabled', 'disabled');
}
});
$("select[id$=data-type]").change(function(){
if($(this).val() == 'ogg'){
restrictOggBitrate($(this), true);
}else{
restrictOggBitrate($(this), false);
}
});
$("select[id$=data-type]").each(function(){
if($(this).val() == 'ogg'){
restrictOggBitrate($(this), true);
}
});
$("select[id$=data-output]").change(function(){
if($(this).val() == 'shoutcast'){
hideForShoutcast($(this));
}else{
showForIcecast($(this));
}
});
$("select[id$=data-output]").each(function(){
if($(this).val() == 'shoutcast'){
hideForShoutcast($(this));
}
});
$('.toggle legend').click(function() {
$(this).parent().toggleClass('closed');
return false;
});
$('.collapsible-header').click(function() {
$(this).next().toggle('fast');
$(this).toggleClass("closed");
return false;
});
setLiveSourceConnectionOverrideListener();
showErrorSections();
checkLiquidsoapStatus();
var userManualAnchorOpen = "<a target='_blank' href='" + USER_MANUAL_URL + "'>";
// qtip for help text
$(".override_help_icon").qtip({
content: {
text: sprintf($.i18n._("If %s is behind a router or firewall, you may need to configure port forwarding and this field information will be incorrect. In this case you will need to manually update this field so it shows the correct host/port/mount that your DJ's need to connect to. The allowed range is between 1024 and 49151."), PRODUCT_NAME)+" "+
sprintf($.i18n._(
"For more details, please read the %s%s Manual%s"), userManualAnchorOpen, PRODUCT_NAME, "</a>")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$(".icecast_metadata_help_icon").qtip({
content: {
text: $.i18n._("Check this option to enable metadata for OGG streams (stream metadata is the track title, artist, and show name that is displayed in an audio player). VLC and mplayer have a serious bug when playing an OGG/VORBIS stream that has metadata information enabled: they will disconnect from the stream after every song. If you are using an OGG stream and your listeners do not require support for these audio players, then feel free to enable this option.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$("#auto_transition_help").qtip({
content: {
text: $.i18n._("Check this box to automatically switch off Master/Show source upon source disconnection.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$("#auto_switch_help").qtip({
content: {
text: $.i18n._("Check this box to automatically switch on Master/Show source upon source connection.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$(".stream_username_help_icon").qtip({
content: {
text: $.i18n._("If your Icecast server expects a username of 'source', this field can be left blank.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$(".admin_username_help_icon").qtip({
content: {
text: $.i18n._("This is the admin username and password for Icecast/SHOUTcast to get listener statistics.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$(".master_username_help_icon").qtip({
content: {
text: $.i18n._("If your live streaming client does not ask for a username, this field should be 'source'.")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
$(".stream_type_help_icon").qtip({
content: {
text: sprintf(
$.i18n._("Some stream types require extra configuration. Details about enabling %sAAC+ Support%s or %sOpus Support%s are provided."),
"<a target='_blank' href='https://wiki.sourcefabric.org/x/NgPQ'>",
"</a>",
"<a target='_blank' href='https://wiki.sourcefabric.org/x/KgPQ'>",
"</a>")
},
hide: {
delay: 500,
fixed: true
},
style: {
border: {
width: 0,
radius: 4
},
classes: "ui-tooltip-dark ui-tooltip-rounded"
},
position: {
my: "left bottom",
at: "right center"
},
});
}
function setSliderForReplayGain(){
$( "#slider-range-max" ).slider({
range: "max",
min: -10,
max: 10,
value: $("#rg_modifier_value").html(),
slide: function( event, ui ) {
$( "#replayGainModifier" ).val( ui.value );
$("#rg_modifier_value").html(ui.value);
}
});
$( "#replayGainModifier" ).val( $( "#slider-range-max" ).slider( "value" ) );
}
function setPseudoAdminPassword(s1, s2, s3, s4) {
if (s1) {
$('#s1_data-admin_pass').val('xxxxxx');
}
if (s2) {
$('#s2_data-admin_pass').val('xxxxxx');
}
if (s3) {
$('#s3_data-admin_pass').val('xxxxxx');
}
if (s4) {
$('#s4_data-admin_pass').val('xxxxxx');
}
}
function getAdminPasswordStatus() {
$.ajax({ url: baseUrl+'Preference/get-admin-password-status/format/json', dataType:"json", success:function(data){
setPseudoAdminPassword(data.s1, data.s2, data.s3, data.s4);
}});
}
$(document).ready(function() {
setupEventListeners();
setSliderForReplayGain();
getAdminPasswordStatus();
var s = $("[name^='customStreamSettings']:checked");
$("[id^='stream_save'], [name^='customStreamSettings']").live('click', function() {
var e = $(this);
if (e[0] == s[0]) { return; }
var confirm_pypo_restart_text = $.i18n._("WARNING: This will restart your stream and may cause a short dropout for your listeners!");
if (confirm(confirm_pypo_restart_text)) {
var data = $('#stream_form').serialize();
var url = baseUrl+'Preference/stream-setting';
$.post(url, {format:"json", data: data}, function(json){
$('#content').empty().append(json.html);
if (json.valid) {
window.location.reload();
}
setupEventListeners();
setSliderForReplayGain();
getAdminPasswordStatus();
});
} else {
if (e.prop('checked')) {
if (e[0] != s[0]) {
e.prop('checked', false);
s.prop('checked', true);
}
}
}
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,566 @@
/**
*
* Full Calendar callback methods.
*
*/
function scheduleRefetchEvents(json) {
if (json.show_error == true) {
alert($.i18n._("The show instance doesn't exist anymore!"));
}
if (json.show_id) {
var dialog_id = parseInt($("#add_show_id").val(), 10);
//if you've deleted the show you are currently editing, close the add show dialog.
if (dialog_id === json.show_id) {
$("#add-show-close").click();
}
}
$("#schedule_calendar").fullCalendar('refetchEvents');
}
function makeTimeStamp(date) {
var sy, sm, sd, h, m, s, timestamp;
sy = date.getFullYear();
sm = date.getMonth() + 1;
sd = date.getDate();
h = date.getHours();
m = date.getMinutes();
s = date.getSeconds();
timestamp = sy + "-" + pad(sm, 2) + "-" + pad(sd, 2) + " " + pad(h, 2) + ":" + pad(m, 2) + ":" + pad(s, 2);
return timestamp;
}
function dayClick(date, allDay, jsEvent, view) {
// The show from will be preloaded if the user is admin or program manager.
// Hence, if the user if DJ then it won't open anything.
if (userType == "S" || userType == "A" || userType == "P") {
var now, today, selected, chosenDate, chosenTime;
now = adjustDateToServerDate(new Date(), serverTimezoneOffset);
if (view.name === "month") {
today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
selected = new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
else {
today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes());
selected = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes());
}
if (selected >= today) {
var addShow = $('.add-button');
//remove the +show button if it exists.
if (addShow.length == 1) {
var span = $(addShow).parent();
$(span).next().remove();
$(span).remove();
}
// get current duration value on the form
var duration_string = $.trim($("#add_show_duration").val());
var duration_info = duration_string.split(" ");
var duration_h = 0;
var duration_m = 0;
if (duration_info[0] != null) {
duration_h = parseInt(duration_info[0], 10);
}
if (duration_info[1] != null) {
duration_m = parseInt(duration_info[1], 10);
}
// duration in milisec
var duration = (duration_h * 60 * 60 * 1000) + (duration_m * 60 * 1000);
var startTime_string;
var startTime = 0;
// get start time value on the form
if (view.name === "month") {
startTime_string = $("#add_show_start_time").val();
var startTime_info = startTime_string.split(':');
if (startTime_info.length == 2) {
var start_time_temp = (parseInt(startTime_info[0], 10) * 60 * 60 * 1000)
+ (parseInt(startTime_info[1], 10) * 60 * 1000);
if (!isNaN(start_time_temp)) {
startTime = start_time_temp;
}
}
} else {
// if in day or week view, selected has all the time info as well
// so we don't ahve to calculate it explicitly
startTime_string = pad(selected.getHours(), 2) + ":" + pad(selected.getMinutes(), 2)
startTime = 0
}
// calculate endDateTime
var endDateTime = new Date(selected.getTime() + startTime + duration);
chosenDate = selected.getFullYear() + '-' + pad(selected.getMonth() + 1, 2) + '-' + pad(selected.getDate(), 2);
var endDateFormat = endDateTime.getFullYear() + '-' + pad(endDateTime.getMonth() + 1, 2) + '-' + pad(endDateTime.getDate(), 2);
//TODO: This should all be refactored into a proper initialize() function for the show form.
$("#add_show_start_now-future").attr('checked', 'checked');
$("#add_show_start_now-now").removeProp('disabled');
setupStartTimeWidgets(); //add-show.js
$("#add_show_start_date").val(chosenDate);
$("#add_show_end_date_no_repeat").val(endDateFormat);
$("#add_show_end_date").val(endDateFormat);
if (view.name !== "month") {
var endTimeString = pad(endDateTime.getHours(), 2) + ":" + pad(endDateTime.getMinutes(), 2);
$("#add_show_start_time").val(startTime_string)
$("#add_show_end_time").val(endTimeString)
}
calculateShowColor();
$("#schedule-show-when").show();
openAddShowForm();
makeAddShowButton();
toggleAddShowButton();
}
}
}
function viewDisplay(view) {
view_name = view.name;
if (view.name === 'agendaDay' || view.name === 'agendaWeek') {
var calendarEl = this;
var select = $('<select class="schedule_change_slots input_select"/>')
.append('<option value="1">' + $.i18n._("1m") + '</option>')
.append('<option value="5">' + $.i18n._("5m") + '</option>')
.append('<option value="10">' + $.i18n._("10m") + '</option>')
.append('<option value="15">' + $.i18n._("15m") + '</option>')
.append('<option value="30">' + $.i18n._("30m") + '</option>')
.append('<option value="60">' + $.i18n._("60m") + '</option>')
.change(function () {
var slotMin = $(this).val();
var opt = view.calendar.options;
var date = $(calendarEl).fullCalendar('getDate');
opt.slotMinutes = parseInt(slotMin);
opt.events = getFullCalendarEvents;
opt.defaultView = view.name;
//re-initialize calendar with new slotmin options
$(calendarEl)
.fullCalendar('destroy')
.fullCalendar(opt)
.fullCalendar('gotoDate', date);
//save slotMin value to db
var url = baseUrl + 'Schedule/set-time-interval/format/json';
$.post(url, { timeInterval: slotMin });
});
var topLeft = $(view.element).find("table.fc-agenda-days > thead th:first");
//select.width(topLeft.width())
// .height(topLeft.height());
topLeft.empty()
.append(select);
var slotMin = view.calendar.options.slotMinutes;
$('.schedule_change_slots option[value="' + slotMin + '"]').attr('selected', 'selected');
}
if (($("#add-show-form").length == 1) && ($("#add-show-form").css('display') == 'none') && ($('.fc-header-left > span').length == 5)) {
//userType is defined in bootstrap.php, and is derived from the currently logged in user.
if (userType == "S" || userType == "A" || userType == "P") {
makeAddShowButton();
}
}
//save view name to db if it was changed
if (calendarPref.timeScale !== view.name) {
var url = baseUrl + 'Schedule/set-time-scale/format/json';
$.post(url, { timeScale: view.name });
calendarPref.timeScale = view.name;
}
}
function eventRender(event, element, view) {
$(element).addClass("fc-show-instance-" + event.id);
$(element).attr("data-show-id", event.showId);
$(element).attr("data-show-linked", event.linked);
$(element).data("event", event);
//only put progress bar on shows that aren't being recorded.
if ((view.name === 'agendaDay' || view.name === 'agendaWeek') && event.record === 0) {
var div = $('<div/>');
div
.height('5px')
.width('95%')
.css('margin-top', '1px')
.css('margin-left', 'auto')
.css('margin-right', 'auto')
.progressbar({
value: event.percent
});
$(element).find(".fc-event-content").append(div);
}
if (event.record === 0 && event.rebroadcast === 0) {
if (view.name === 'agendaDay' || view.name === 'agendaWeek') {
if (event.show_empty === 1) {
if (event.linked) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span><span class="small-icon show-empty"></span>');
// in theory a linked show shouldn't have an automatic playlist so adding this here
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span>');
}
else {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon show-empty"></span>');
}
} else if (event.show_partial_filled === true) {
if (event.linked) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span><span class="small-icon show-partial-filled"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span>');
} else {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon show-partial-filled"></span>');
}
} else if (event.percent > 100) {
if (event.linked) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span><span class="small-icon show-overbooked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span>');
} else {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon show-overbooked"></span>');
}
} else {
if (event.linked) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span>');
}
}
} else if (view.name === 'month') {
if (event.show_empty === 1) {
if (event.linked) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="' + $.i18n._("Show is empty") + '" class="small-icon show-empty"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Show has an automatic playlist") + '"class="small-icon autoplaylist"></span>');
} else {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Show is empty") + '" class="small-icon show-empty"></span>');
}
} else if (event.show_partial_filled === true) {
if (event.linked) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="' + $.i18n._("Show is partially filled") + '" class="small-icon show-partial-filled"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Show has an automatic playlist") + '"class="small-icon autoplaylist"></span>');
} else {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Show is partially filled") + '" class="small-icon show-partial-filled"></span>');
}
} else if (event.percent > 100) {
if (event.linked) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="' + $.i18n._("Shows longer than their scheduled time will be cut off by a following show.") + '" class="small-icon show-overbooked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Show has an automatic playlist") + '"class="small-icon autoplaylist"></span>');
} else {
$(element)
.find(".fc-event-title")
.after('<span title="' + $.i18n._("Shows longer than their scheduled time will be cut off by a following show.") + '" class="small-icon show-overbooked"></span>');
}
} else {
if (event.linked) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon autoplaylist"></span>');
}
}
}
}
//rebroadcast icon
if (event.rebroadcast === 1) {
if (view.name === 'agendaDay' || view.name === 'agendaWeek') {
$(element).find(".fc-event-time").before('<span class="small-icon rebroadcast"></span>');
} else if (view.name === 'month') {
$(element).find(".fc-event-title").after('<span class="small-icon rebroadcast"></span>');
}
}
//now playing icon.
var span = '<span class="small-icon now-playing"></span>';
if (event.nowPlaying === true) {
if (view_name === 'agendaDay' || view_name === 'agendaWeek') {
$(element).find(".fc-event-time").before(span);
}
else if (view_name === 'month') {
$(element).find(".fc-event-title").after(span);
}
}
}
function eventAfterRender(event, element, view) {
$(element).find(".small-icon").live('mouseover', function () {
addQtipsToIcons($(this), event.id);
});
}
function eventDrop(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
var url = baseUrl + 'Schedule/move-show/format/json';
$.post(url,
{ day: dayDelta, min: minuteDelta, showInstanceId: event.id },
function (json) {
if (json.show_error == true) {
alertShowErrorAndReload();
}
if (json.error) {
alert(json.error);
revertFunc();
}
//Workaround for cases where FullCalendar handles events over DST
//time changes in a different way than Airtime does.
//(Airtime preserves show duration, FullCalendar doesn't.)
scheduleRefetchEvents(json);
});
}
function eventResize(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view) {
var url = baseUrl + 'Schedule/resize-show/format/json';
$.post(url,
{ day: dayDelta, min: minuteDelta, showId: event.showId, instanceId: event.id },
function (json) {
if (json.show_error == true) {
alertShowErrorAndReload();
}
if (json.error) {
alert(json.error);
revertFunc();
}
scheduleRefetchEvents(json);
});
}
function windowResize() {
// 200 px for top dashboard and 50 for padding on main content
// this calculation was copied from schedule.js line 326
var mainHeight = $(window).height() - 200 - 24;
$('#schedule_calendar').fullCalendar('option', 'contentHeight', mainHeight);
}
function preloadEventFeed() {
createFullCalendar({ calendarInit: calendarPref });
}
var initialLoad = true;
function getFullCalendarEvents(start, end, callback) {
if (initialLoad) {
initialLoad = false;
callback(calendarEvents);
} else {
var url, start_date, end_date;
start_date = makeTimeStamp(start);
end_date = makeTimeStamp(end);
url = baseUrl + 'Schedule/event-feed';
var d = new Date();
$.post(url, { format: "json", start: start_date, end: end_date, cachep: d.getTime() }, function (json) {
callback(json.events);
getUsabilityHint();
});
}
$(".fc-button").addClass("btn").addClass("btn-small");
//$("span.fc-button > :button").addClass("btn btn-small");
}
/** This function adds and removes the current
* show icon
*/
function getCurrentShow() {
var url = baseUrl + 'Schedule/get-current-show/format/json';
function addNowPlaying(json) {
var $el,
span = '<span class="small-icon now-playing"></span>';
$(".now-playing").remove();
if (json.current_show === true) {
$el = $(".fc-show-instance-" + json.si_id);
if (view_name === 'agendaDay' || view_name === 'agendaWeek') {
$el.find(".fc-event-time").before(span);
}
else if (view_name === 'month') {
$el.find(".fc-event-title").after(span);
}
}
setTimeout(getCurrentShow, 5000);
}
$.post(url, { format: "json" }, addNowPlaying);
}
function addQtipsToIcons(ele, id) {
if ($(ele).hasClass("progress")) {
$(ele).qtip({
content: {
text: $.i18n._("Uploading in progress...")
},
position: {
adjust: {
resize: true,
method: "flip flip"
},
at: "right center",
my: "left top",
viewport: $(window)
},
style: {
classes: "ui-tooltip-dark file-md-long"
},
show: {
ready: true // Needed to make it show on first mouseover event
}
});
} else if ($(ele).hasClass("show-empty")) {
$(ele).qtip({
content: {
text: $.i18n._("This show has no scheduled content.")
},
position: {
adjust: {
resize: true,
method: "flip flip"
},
at: "right center",
my: "left top",
viewport: $(window)
},
style: {
classes: "ui-tooltip-dark file-md-long"
},
show: {
ready: true // Needed to make it show on first mouseover event
}
});
} else if ($(ele).hasClass("show-partial-filled")) {
$(ele).qtip({
content: {
text: $.i18n._("This show is not completely filled with content.")
},
position: {
adjust: {
resize: true,
method: "flip flip"
},
at: "right center",
my: "left top",
viewport: $(window)
},
style: {
classes: "ui-tooltip-dark file-md-long"
},
show: {
ready: true // Needed to make it show on first mouseover event
}
});
} else if ($(ele).hasClass("show-overbooked")) {
$(ele).qtip({
content: {
text: $.i18n._("Shows longer than their scheduled time will be cut off by a following show.")
},
position: {
adjust: {
resize: true,
method: "flip flip"
},
at: "right center",
my: "left top",
viewport: $(window)
},
style: {
classes: "ui-tooltip-dark file-md-long"
},
show: {
ready: true // Needed to make it show on first mouseover event
}
});
}
}
//Alert the error and reload the page
//this function is used to resolve concurrency issue
function alertShowErrorAndReload() {
alert($.i18n._("The show instance doesn't exist anymore!"));
window.location.reload();
}
$(document).ready(function () {
preloadEventFeed();
getCurrentShow();
});
var view_name;

View file

@ -0,0 +1,511 @@
var AIRTIME = (function(AIRTIME){
var mod;
if (AIRTIME.schedule === undefined) {
AIRTIME.schedule = {};
}
mod = AIRTIME.schedule;
return AIRTIME;
}(AIRTIME || {}));
var serverTimezoneOffset = 0;
function closeDialogCalendar(event, ui) {
$el = $(this);
$el.dialog('destroy');
$el.remove();
//need to refetch the events to update scheduled status.
$("#schedule_calendar").fullCalendar( 'refetchEvents' );
}
function confirmCancelShow(show_instance_id){
if (confirm($.i18n._('Cancel Current Show?'))) {
var url = baseUrl+"Schedule/cancel-current-show";
$.ajax({
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
});
}
}
function confirmCancelRecordedShow(show_instance_id){
if (confirm($.i18n._('Stop recording current show?'))) {
var url = baseUrl+"Schedule/cancel-current-show";
$.ajax({
url: url,
data: {format: "json", id: show_instance_id},
success: function(data){
scheduleRefetchEvents(data);
}
});
}
}
function findViewportDimensions() {
var viewportwidth,
viewportheight;
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
}
// IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document)
else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight;
}
// older versions of IE
else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
return {
width: viewportwidth,
height: viewportheight-45
};
}
function highlightMediaTypeSelector(dialog) {
var selected;
if (location.hash === "") {
selected = dialog.find("a[href$='#tracks']");
} else {
selected = dialog.find("a[href$='"+location.hash+"']")
}
selected.parent().addClass("selected");
$("#library_filter").text(selected.text());
// Slightly hacky way of triggering the click event when it's outside of the anchor text
dialog.find(".media_type_selector").on("click", function() {
// Need get(0) here so we don't create a stack overflow by recurring the click on the parent
$(this).find("a").get(0).click();
});
$(window).on('hashchange', function() {
var selected = dialog.find("a[href$='"+location.hash+"']");
AIRTIME.library.selectNone();
dialog.find(".media_type_selector").each(function () {
$(this).removeClass("selected");
});
$("#library_filter").text(selected.text());
selected.parent().addClass("selected");
oTable.fnDraw();
});
}
function buildTimerange(dialog) {
var builder = dialog.find("#show_builder"),
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
//i18n_months, i18n_days_short are in common.js
monthNames: i18n_months,
dayNamesMin: i18n_days_short,
onClick: function(sDate, oDatePicker) {
$(this).datepicker("setDate", sDate);
},
onClose: validateTimeRange
},
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
closeButtonText: $.i18n._("Done"),
showLeadingZero: false,
defaultTime: '0:00',
hourText: $.i18n._("Hour"),
minuteText: $.i18n._("Minute"),
onClose: validateTimeRange
};
/*
* Icon hover states for search.
*/
builder.on("mouseenter", ".sb-timerange .ui-button", function(ev) {
$(this).addClass("ui-state-hover");
});
builder.on("mouseleave", ".sb-timerange .ui-button", function(ev) {
$(this).removeClass("ui-state-hover");
});
builder.find(dateStartId)
.datepicker(oBaseDatePickerSettings);
builder.find(timeStartId)
.timepicker(oBaseTimePickerSettings);
builder.find(dateEndId)
.datepicker(oBaseDatePickerSettings);
builder.find(timeEndId)
.timepicker(oBaseTimePickerSettings);
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId,
dateEndId, timeEndId);
AIRTIME.showbuilder.fnServerData.start = oRange.start;
AIRTIME.showbuilder.fnServerData.end = oRange.end;
}
function buildScheduleDialog (json, instance_id) {
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = Math.floor(viewport.height * 0.96),
width = Math.floor(viewport.width * 0.96),
fnServer = AIRTIME.showbuilder.fnServerData;
dialog.dialog({
autoOpen: false,
title: json.title,
width: width,
height: height,
resizable: false,
draggable: true,
modal: true,
close: closeDialogCalendar,
buttons: [
{
text: $.i18n._("Ok"),
class: "btn",
click: function() {
$(this).dialog("close");
//getUsabilityHint();
}
}
]
});
//set the start end times so the builder datatables knows its time range.
fnServer.start = json.start;
fnServer.end = json.end;
fnServer.ops = {};
fnServer.ops.showFilter = 0;
fnServer.ops.showInstanceFilter = instance_id;
fnServer.ops.myShows = 0;
AIRTIME.library.libraryInit();
AIRTIME.showbuilder.builderDataTable();
dialog.dialog('open');
highlightMediaTypeSelector(dialog);
buildTimerange(dialog);
}
function buildContentDialog (json){
var dialog = $(json.dialog),
viewport = findViewportDimensions(),
height = viewport.height * 2/3,
width = viewport.width * 4/5;
if (json.show_error == true){
alertShowErrorAndReload();
}
dialog.find("#show_progressbar").progressbar({
value: json.percentFilled
});
dialog.dialog({
autoOpen: false,
title: $.i18n._("Contents of Show") +" '" + json.showTitle + "'",
width: width,
height: height,
modal: true,
close: closeDialogCalendar,
buttons: [
{
text: $.i18n._("Ok"),
"class": "btn",
click: function() {
dialog.remove();
}
}
]
});
dialog.dialog('open');
}
/**
* Use user preference for time scale; defaults to month if preference was never set
*/
function getTimeScalePreference(data) {
return data.calendarInit.timeScale;
}
/**
* Use user preference for time interval; defaults to 30m if preference was never set
*/
function getTimeIntervalPreference(data) {
return parseInt(data.calendarInit.timeInterval);
}
function createFullCalendar(data){
serverTimezoneOffset = data.calendarInit.timezoneOffset;
var mainHeight = $(window).height() - 200 - 35;
$('#schedule_calendar').fullCalendar({
header: {
left: 'prev, next, today',
center: 'title',
right: 'agendaDay, agendaWeek, month'
},
defaultView: getTimeScalePreference(data),
slotMinutes: getTimeIntervalPreference(data),
firstDay: data.calendarInit.weekStartDay,
editable: false,
allDaySlot: false,
axisFormat: 'H:mm',
timeFormat: {
agenda: 'H:mm{ - H:mm}',
month: 'H:mm{ - H:mm}'
},
//i18n_months is in common.js
monthNames: i18n_months,
monthNamesShort: [
$.i18n._('Jan'),
$.i18n._('Feb'),
$.i18n._('Mar'),
$.i18n._('Apr'),
$.i18n._('May'),
$.i18n._('Jun'),
$.i18n._('Jul'),
$.i18n._('Aug'),
$.i18n._('Sep'),
$.i18n._('Oct'),
$.i18n._('Nov'),
$.i18n._('Dec')
],
buttonText: {
today: $.i18n._('Today'),
month: $.i18n._('Month'),
week: $.i18n._('Week'),
day: $.i18n._('Day')
},
dayNames: [
$.i18n._('Sunday'),
$.i18n._('Monday'),
$.i18n._('Tuesday'),
$.i18n._('Wednesday'),
$.i18n._('Thursday'),
$.i18n._('Friday'),
$.i18n._('Saturday')
],
dayNamesShort: [
$.i18n._('Sun'),
$.i18n._('Mon'),
$.i18n._('Tue'),
$.i18n._('Wed'),
$.i18n._('Thu'),
$.i18n._('Fri'),
$.i18n._('Sat')
],
contentHeight: mainHeight,
theme: true,
lazyFetching: true,
serverTimestamp: parseInt(data.calendarInit.timestamp, 10),
serverTimezoneOffset: parseInt(data.calendarInit.timezoneOffset, 10),
events: getFullCalendarEvents,
//callbacks (in full-calendar-functions.js)
viewDisplay: viewDisplay,
dayClick: dayClick,
eventRender: eventRender,
eventAfterRender: eventAfterRender,
eventDrop: eventDrop,
eventResize: eventResize,
windowResize: windowResize
});
}
//Alert the error and reload the page
//this function is used to resolve concurrency issue
function alertShowErrorAndReload(){
alert($.i18n._("The show instance doesn't exist anymore!"));
window.location.reload();
}
$(document).ready(function() {
$.contextMenu({
selector: 'div.fc-event',
trigger: "left",
ignoreRightClick: true,
className: 'calendar-context-menu',
build: function($el, e) {
var data,
items,
callback;
data = $el.data("event");
function processMenuItems(oItems) {
//define a schedule callback.
if (oItems.schedule !== undefined) {
callback = function() {
$.post(oItems.schedule.url, {format: "json", id: data.id}, function(json){
buildScheduleDialog(json, data.id);
});
};
oItems.schedule.callback = callback;
}
//define a clear callback.
if (oItems.clear !== undefined) {
callback = function() {
if (confirm($.i18n._("Remove all content?"))) {
$.post(oItems.clear.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
}
};
oItems.clear.callback = callback;
}
//define an edit callback.
if (oItems.edit !== undefined) {
if(oItems.edit.items !== undefined){
var edit = oItems.edit.items;
//edit a single instance
callback = function() {
$.get(edit.instance.url, {format: "json", showId: data.showId, instanceId: data.id, type: "instance"}, function(json){
beginEditShow(json);
});
};
edit.instance.callback = callback;
//edit this instance and all
callback = function() {
$.get(edit.all.url, {format: "json", showId: data.showId, instanceId: data.id, type: "all"}, function(json){
beginEditShow(json);
});
};
edit.all.callback = callback;
}else{
callback = function() {
$.get(oItems.edit.url, {format: "json", showId: data.showId, instanceId: data.id, type: oItems.edit._type}, function(json){
beginEditShow(json);
});
};
oItems.edit.callback = callback;
}
}
//define a content callback.
if (oItems.content !== undefined) {
callback = function() {
$.get(oItems.content.url, {format: "json", id: data.id}, function(json){
buildContentDialog(json);
});
};
oItems.content.callback = callback;
}
//define a cancel recorded show callback.
if (oItems.cancel_recorded !== undefined) {
callback = function() {
confirmCancelRecordedShow(data.id);
};
oItems.cancel_recorded.callback = callback;
}
//define a view recorded callback.
if (oItems.view_recorded !== undefined) {
callback = function() {
$.get(oItems.view_recorded.url, {format: "json"}, function(json){
//in library.js
buildEditMetadataDialog(json);
});
};
oItems.view_recorded.callback = callback;
}
//define a cancel callback.
if (oItems.cancel !== undefined) {
callback = function() {
confirmCancelShow(data.id);
};
oItems.cancel.callback = callback;
}
//define a delete callback.
if (oItems.del !== undefined) {
//repeating show multiple delete options
if (oItems.del.items !== undefined) {
var del = oItems.del.items;
//delete a single instance
callback = function() {
$.post(del.single.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.single.callback = callback;
//delete this instance and all following instances.
callback = function() {
$.post(del.following.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
del.following.callback = callback;
}
//single show
else {
callback = function() {
$.post(oItems.del.url, {format: "json", id: data.id}, function(json){
scheduleRefetchEvents(json);
});
};
oItems.del.callback = callback;
}
}
items = oItems;
}
$.ajax({
url: baseUrl+"schedule/make-context-menu",
type: "GET",
data: {instanceId : data.id, showId: data.showId, format: "json"},
dataType: "json",
async: false,
success: function(json){
processMenuItems(json.items);
}
});
return {
className: 'calendar-context-menu',
items: items,
determinePosition : function($menu, x, y) {
$menu.css('display', 'block')
.position({ my: "left top", at: "right top", of: this, offset: "-20 10", collision: "fit"})
.css('display', 'none');
}
};
}
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,376 @@
AIRTIME = (function(AIRTIME) {
var viewport,
$lib,
$libWrapper,
$builder,
$fs,
widgetHeight,
screenWidth,
resizeTimeout,
oBaseDatePickerSettings,
oBaseTimePickerSettings,
oRange,
dateStartId = "#sb_date_start",
timeStartId = "#sb_time_start",
dateEndId = "#sb_date_end",
timeEndId = "#sb_time_end",
mod;
if (AIRTIME.builderMain === undefined) {
AIRTIME.builderMain = {};
}
mod = AIRTIME.builderMain;
oBaseDatePickerSettings = {
dateFormat: 'yy-mm-dd',
//i18n_months, i18n_days_short are in common.js
monthNames: i18n_months,
dayNamesMin: i18n_days_short,
onClick: function(sDate, oDatePicker) {
$(this).datepicker( "setDate", sDate );
},
onClose: validateTimeRange
};
oBaseTimePickerSettings = {
showPeriodLabels: false,
showCloseButton: true,
closeButtonText: $.i18n._("Done"),
showLeadingZero: false,
defaultTime: '0:00',
hourText: $.i18n._("Hour"),
minuteText: $.i18n._("Minute"),
onClose: validateTimeRange
};
function setWidgetSize() {
viewport = AIRTIME.utilities.findViewportDimensions();
widgetHeight = viewport.height - 180;
screenWidth = Math.floor(viewport.width - 50);
var libTableHeight = widgetHeight - 175,
builderTableHeight = widgetHeight - 95,
oTable;
if ($fs.is(':visible')) {
builderTableHeight = builderTableHeight - 40;
}
//set the heights of the main widgets.
$builder//.height(widgetHeight)
.find(".dataTables_scrolling")
//.css("max-height", builderTableHeight)
.end();
//.width(screenWidth);
$lib//.height(widgetHeight)
.find(".dataTables_scrolling")
//.css("max-height", libTableHeight)
.end();
if ($lib.filter(':visible').length > 0) {
//$lib.width(Math.floor(screenWidth * 0.47));
$builder//.width(Math.floor(screenWidth * 0.47))
.find("#sb_edit")
.remove()
.end()
.find("#sb_date_start")
.css("margin-left", 0)
.end();
oTable = $('#show_builder_table').dataTable();
//oTable.fnDraw();
}
}
function showSearchSubmit() {
var fn,
op,
oTable = $('#show_builder_table').dataTable(),
check;
check = validateTimeRange();
if (check.isValid) {
//reset timestamp value since input values could have changed.
AIRTIME.showbuilder.resetTimestamp();
fn = oTable.fnSettings().fnServerData;
fn.start = check.start;
fn.end = check.end;
op = $("div.sb-options-form");
if (op.is(":visible")) {
if (fn.ops === undefined) {
fn.ops = {};
}
fn.ops.showFilter = op.find("#sb_show_filter").val();
// Hacky?
fn.ops.myShows = (fn.ops.showFilter == -1) ? 1 : 0;
}
oTable.fnDraw();
}
}
function highlightMediaTypeSelector()
{
$(".media_type_selector:first").addClass("highlight");
var selected;
if (location.hash === "") {
location.hash = "tracks";
selected = $("a[href$='#tracks']");
} else {
selected = $("a[href$='"+location.hash+"']");
}
selected.parent().addClass("selected");
$("#library_filter").text(selected.text());
$(window).on('hashchange', function() {
var selected = $("a[href$='"+location.hash+"']"),
dashboardLink = $(".media_type_selector:first"),
tableType;
if (selected.parent().data("selection-id") == AIRTIME.library.MediaTypeIntegerEnum.PODCAST) {
tableType = AIRTIME.library.DataTableTypeEnum.PODCAST;
} else {
tableType = AIRTIME.library.DataTableTypeEnum.LIBRARY;
}
dashboardLink.find("a").attr("href", selected.attr("href"));
AIRTIME.library.selectNone();
$(".media_type_selector").each(function () {
$(this).removeClass("selected");
});
selected.parent().addClass("selected");
AIRTIME.library.setCurrentTable(tableType);
$("#library_filter").text(selected.text());
// Highlight the dashboard link
dashboardLink.addClass("highlight");
});
}
mod.onReady = function() {
// Normally we would just use audio/*, but it includes file types that we can't handle (like .m4a)
// We initialize the acceptedMimeTypes variable in Bootstrap so we don't have to duplicate the list
Dropzone.options.content = {
url:'/rest/media',
clickable: false,
acceptedFiles: acceptedMimeTypes.join(),
init: function () {
this.on("sending", function (file, xhr, data) {
data.append("csrf_token", $("#csrf").val());
});
},
dictDefaultMessage: '',
createImageThumbnails: false,
previewTemplate : '<div style="display:none"></div>'
};
// define module vars.
$lib = $("#library_content");
$builder = $("#show_builder");
$fs = $builder.find('fieldset');
//Highlight the media type selector we're currently on.
highlightMediaTypeSelector();
// always re-show builder if dashboard button was clicked
$('.media_type_selector:first').on('click', function() {
$builder.show();
AIRTIME.tabs.openScheduleTab();
});
/*
* Icon hover states for search.
*/
$builder.on("mouseenter", ".sb-timerange .ui-button", function(ev) {
$(this).addClass("ui-state-hover");
});
$builder.on("mouseleave", ".sb-timerange .ui-button", function(ev) {
$(this).removeClass("ui-state-hover");
});
$builder.find(dateStartId)
.datepicker(oBaseDatePickerSettings)
.blur(validateTimeRange);
$builder.find(timeStartId)
.timepicker(oBaseTimePickerSettings)
.blur(validateTimeRange);
$builder.find(dateEndId)
.datepicker(oBaseDatePickerSettings)
.blur(validateTimeRange);
$builder.find(timeEndId)
.timepicker(oBaseTimePickerSettings)
.blur(validateTimeRange);
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId,
dateEndId, timeEndId);
AIRTIME.showbuilder.fnServerData.start = oRange.start;
AIRTIME.showbuilder.fnServerData.end = oRange.end;
//the user might not have the library on the page (guest user)
if (AIRTIME.library !== undefined) {
AIRTIME.library.libraryInit();
}
AIRTIME.showbuilder.builderDataTable();
setWidgetSize();
$libWrapper = $lib.find("#library_display_wrapper");
//$builder.find('.dataTables_scrolling').css("max-height",
// widgetHeight - 95);
$builder.on("click", "#sb_submit", showSearchSubmit);
$builder.on("click", "#sb_edit", function(ev) {
var schedTable = $("#show_builder_table").dataTable();
// reset timestamp to redraw the cursors.
AIRTIME.showbuilder.resetTimestamp();
$lib.show().width(Math.floor(screenWidth * 0.48));
$builder.width(Math.floor(screenWidth * 0.48)).find("#sb_edit")
.remove().end().find("#sb_date_start")
.css("margin-left", 0).end();
schedTable.fnDraw();
$.ajax( {
url : baseUrl+"usersettings/set-now-playing-screen-settings",
type : "POST",
data : {
settings : {
library : true
},
format : "json"
},
dataType : "json",
success : function() {
}
});
});
$lib.on("click", "#sb_lib_close", function() {
var schedTable = $("#show_builder_table").dataTable();
$lib.hide();
$builder.width(screenWidth).find(".sb-timerange").find("#sb_date_start").css("margin-left", 30)
.end().end();
schedTable.fnDraw();
$.ajax( {
url : baseUrl+"usersettings/set-now-playing-screen-settings",
type : "POST",
data : {
settings : {
library : false
},
format : "json"
},
dataType : "json",
success : function() {
}
});
});
$builder.find('legend').click(
function(ev, item) {
if ($fs.hasClass("closed")) {
$fs.removeClass("closed");
//$builder.find('.dataTables_scrolling').css(
// "max-height", widgetHeight - 150);
} else {
$fs.addClass("closed");
// set defaults for the options.
$fs.find('select').val(0);
$fs.find('input[type="checkbox"]').attr("checked",
false);
//$builder.find('.dataTables_scrolling').css(
// "max-height", widgetHeight - 110);
}
});
// set click event for all my shows checkbox.
$builder.on("click", "#sb_my_shows", function(ev) {
if ($(this).is(':checked')) {
$(ev.delegateTarget).find('#sb_show_filter').val(0);
}
showSearchSubmit();
});
//set select event for choosing a show.
$builder.on("change", '#sb_show_filter', function(ev) {
if ($(this).val() !== 0) {
$(ev.delegateTarget).find('#sb_my_shows')
.attr("checked", false);
}
showSearchSubmit();
});
function checkScheduleUpdates() {
var data = {}, oTable = $('#show_builder_table').dataTable(), fn = oTable
.fnSettings().fnServerData, start = fn.start, end = fn.end;
data["format"] = "json";
data["start"] = start;
data["end"] = end;
data["timestamp"] = AIRTIME.showbuilder.getTimestamp();
data["instances"] = AIRTIME.showbuilder.getShowInstances();
if (fn.hasOwnProperty("ops")) {
data["myShows"] = fn.ops.myShows;
data["showFilter"] = fn.ops.showFilter;
data["showInstanceFilter"] = fn.ops.showInstanceFilter;
}
$.ajax( {
"dataType" : "json",
"type" : "GET",
"url" : baseUrl+"showbuilder/check-builder-feed",
"data" : data,
"success" : function(json) {
if (json.update === true) {
oTable.fnDraw();
}
setTimeout(checkScheduleUpdates, 5000);
}
});
//check if the timeline view needs updating.
setTimeout(checkScheduleUpdates, 5000);
}
};
mod.onResize = function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(setWidgetSize, 100);
};
return AIRTIME;
} (AIRTIME || {}));
$(document).ready(AIRTIME.builderMain.onReady);
$(window).resize(AIRTIME.builderMain.onResize);

View file

@ -0,0 +1,426 @@
var AIRTIME = (function(AIRTIME){
/**
* AIRTIME module namespace object
*/
var mod,
/**
* Tab counter to use as unique tab IDs that can be
* retrieved from the DOM
*
* @type {number}
*/
$tabCount = 0,
/**
* Map of Tab IDs (by tabCount) to object UIDs so
* Tabs can be referenced either by ID (from the DOM)
* or by UID (from object data)
*
* @type {{}}
*/
$tabMap = {},
/**
* Map of object UIDs to currently open Tab objects
*
* @type {{}}
*/
$openTabs = {},
/**
* The currently active (open) Tab object
*
* @type {Tab}
*/
$activeTab,
/**
* Singleton object used to reference the schedule tab
*
* @type {ScheduleTab}
*/
$scheduleTab;
if (AIRTIME.tabs === undefined) {
AIRTIME.tabs = {};
}
mod = AIRTIME.tabs;
/* #####################################################
Object Initialization and Functions
##################################################### */
/**
* Tab object constructor
*
* @param {string} html the HTML to render as the tab contents
* @param {string} uid the unique ID for the tab. Uses the values in
* AIRTIME.library.MediaTypeStringEnum and the object ID
* to create a string of the form TYPE_ID.
* @returns {Tab} the created Tab object
* @constructor
*/
var Tab = function(html, uid) {
var self = this;
AIRTIME.library.selectNone();
var existingTab = $openTabs[uid];
if (existingTab) {
existingTab.switchTo();
return existingTab;
}
self.id = ++$tabCount;
self.uid = uid;
// TODO: clean this up a bit and use js instead of strings to create elements
var wrapper = "<div data-tab-id='" + self.id + "' id='pl-tab-content-" + self.id + "' class='side_playlist pl-content'><div class='editor_pane_wrapper'></div></div>",
t = $("#show_builder").append(wrapper).find("#pl-tab-content-" + self.id),
pane = $(".editor_pane_wrapper:last").append(html),
name = pane.find("#track_title").length > 0 ? pane.find("#track_title").val() + $.i18n._(" - Metadata Editor")
: pane.find(".playlist_name_display").val(),
tab =
"<li data-tab-id='" + self.id + "' id='pl-tab-" + self.id + "' role='presentation' class='active'>" +
"<a href='javascript:void(0)'>" +
"<span class='tab-name'>" + name + "</span>" +
"<span href='#' class='lib_pl_close icon-remove'></span>" +
"</a>" +
"</li>",
tabs = $(".nav.nav-tabs");
$(".nav.nav-tabs li").removeClass("active");
tabs.append(tab);
var newTab = $("#pl-tab-" + self.id);
self.wrapper = pane;
self.contents = t;
self.tab = newTab;
$openTabs[uid] = self;
$tabMap[self.id] = uid;
self._init();
self.switchTo();
return self;
};
/**
* Private initialization function for Tab objects
*
* Assigns default action handlers to the tab DOM element
*
* @private
*/
Tab.prototype._init = function() {
var self = this;
self.assignTabClickHandler(function(e) {
if (!$(this).hasClass('active')) {
self.switchTo();
}
});
self.assignTabCloseClickHandler(function(e) {
e.preventDefault();
e.stopPropagation();
$(this).unbind("click"); // Prevent repeated clicks in quick succession from closing multiple tabs
// We need to update the text on the add button
AIRTIME.library.checkAddButton();
// We also need to run the draw callback to update how dragged items are drawn
AIRTIME.library.fnDrawCallback();
self.close();
});
self.contents.on("click", ".toggle-editor-form", function(event) {
self.contents.find(".inner_editor_wrapper").slideToggle(200);
var buttonIcon = $(this).find('.icon-white');
buttonIcon.toggleClass('icon-chevron-up');
buttonIcon.toggleClass('icon-chevron-down');
});
};
/**
* Internal destructor. Can be assigned via assignOnCloseHandler
*
* @private
*/
Tab.prototype._destroy = function () {};
/**
* Assign the given function f as the click handler for the tab
*
* @param {function} f the function to call when the tab is clicked
*/
Tab.prototype.assignTabClickHandler = function(f) {
var self = this;
self.tab.unbind("click").on("click", function (e) {
// Always close on middle mouse press
if (e.which == 2) {
// Simulate a click on the close tab button so any
// additional on-close behaviour is executed
self.tab.find(".lib_pl_close").click();
return;
}
f();
});
};
/**
* Assign the given function f as the click handler for the tab close button
*
* @param {function} f the function to call when the tab's close button is clicked
*/
Tab.prototype.assignTabCloseClickHandler = function(f) {
this.tab.find(".lib_pl_close").unbind("click").click(f);
};
/**
* Assign an implicit destructor
*
* @param {function} fn function to run when this Tab is destroyed
*/
Tab.prototype.assignOnCloseHandler = function (fn) {
this._destroy = fn;
};
/**
* Open this tab in the right-hand pane and set it as the currently active tab
*/
Tab.prototype.switchTo = function() {
var self = this;
$activeTab.contents.hide().removeClass("active-tab");
self.contents.addClass("active-tab").show();
$activeTab.tab.removeClass("active");
self.tab.addClass("active");
mod.updateActiveTab();
// In case we're adding a tab that wraps to the next row
// It's better to call this here so we don't have to call it in multiple places
mod.onResize();
return this; // For chaining
};
/**
* Close the tab. Switches to the nearest open tab, prioritizing the
* more recent (rightmost) tabs
*/
Tab.prototype.close = function() {
var self = this;
var ascTabs = Object.keys($openTabs).sort(function(a, b){return a-b}),
pos = ascTabs.indexOf(self.uid),
toTab = pos < ascTabs.length-1 ? $openTabs[ascTabs[++pos]] : $openTabs[ascTabs[--pos]];
delete $openTabs[self.uid]; // Remove this tab from the open tab array
delete $tabMap[self.id]; // Remove this tab from the internal tab mapping
// Remove the relevant DOM elements (the tab and its contents)
if (self.uid !== 0) {
self.tab.remove();
self.contents.remove();
} else {
// only hide scheduled shows tab so we can still interact with it.
self.tab.hide();
self.contents.hide();
}
if (self.isActive() && toTab) { // Closing the current tab, otherwise we don't need to switch tabs
toTab.switchTo();
} else {
mod.onResize();
}
if (Object.keys($openTabs).length < 1) {
$('#show_builder').hide();
}
self._destroy();
};
/**
* Set the visible Tab name to the given string
*
* @param {string} name the name to set
*/
Tab.prototype.setName = function(name) {
this.tab.find(".tab-name").text(name);
return this; // For chaining
};
/**
* Check if the Tab object is the currently active (open) Tab
*
* @returns {boolean} true if the Tab is the currently active Tab
*/
Tab.prototype.isActive = function() {
return this.contents.get(0) == $activeTab.contents.get(0);
};
/**
* ScheduledTab object constructor
*
* The schedule tab is present in the DOM already on load, and we
* need to be able to reference it in the same way as other tabs
* (to avoid duplication and confusion) so we define it statically
*
* @constructor
*/
var ScheduleTab = function() {
var self = this, uid = 0,
tab = $("#schedule-tab"),
pane = $("#show_builder"),
contents = pane.find(".outer-datatable-wrapper");
self.id = 0;
self.uid = uid;
tab.data("tab-id", self.id);
self.wrapper = pane;
self.contents = contents;
self.tab = tab;
self.assignTabClickHandler(function(e) {
if (!self.isActive()) {
self.switchTo();
}
});
self.assignTabCloseClickHandler(function(e) {
self.close();
});
$openTabs[uid] = self;
$tabMap[self.id] = uid;
};
/**
* Subclass the Tab object
* @type {Tab}
*/
ScheduleTab.prototype = Object.create(Tab.prototype);
ScheduleTab.prototype.constructor = ScheduleTab;
/* #####################################################
Module Functions
##################################################### */
/**
* Initialize the singleton ScheduleTab object on startup
*/
mod.initScheduleTab = function() {
$scheduleTab = new ScheduleTab();
$activeTab = $scheduleTab;
};
/**
* Create a new Tab object and open it in the ShowBuilder pane
*
* @param {string} html the HTML to render as the tab contents
* @param {string} uid the unique ID for the tab. Uses the values in
* AIRTIME.library.MediaTypeStringEnum and the object ID
* @param {function} callback an optional callback function to call once the
* Tab object is initialized
* @returns {Tab} the created Tab object
*/
mod.openTab = function(html, uid, callback) {
$('#show_builder').show();
var newTab = new Tab(html, uid);
if (callback) callback(newTab);
return newTab;
};
/**
* open the schedule tab if if was closed
*
* @returns {Tab}
*/
mod.openScheduleTab = function() {
var $scheduleTab = this.getScheduleTab();
$('#show_builder').show();
$openTabs[0] = $scheduleTab;
$scheduleTab.tab.show();
$scheduleTab.contents.show();
$scheduleTab.switchTo();
$scheduleTab.assignTabCloseClickHandler(function(e) {
$scheduleTab.close();
});
};
/**
* Updates the currently active tab
*
* Called when the user switches tabs for any reason
*
* NOTE: this function updates the currently active playlist
* as a side-effect, which is necessary for playlist tabs
* but not for other types of tabs... would be good to
* get rid of this dependency at some point
*/
mod.updateActiveTab = function() {
var t = $(".nav.nav-tabs .active");
$activeTab = mod.get(t.data("tab-id"));
if (!$activeTab) $activeTab = $scheduleTab;
if ($activeTab.contents.hasClass("pl-content")) {
AIRTIME.playlist.setCurrent($activeTab.contents);
}
};
/**
* Get the ScheduleTab object
*
* @returns {ScheduleTab}
*/
mod.getScheduleTab = function() {
return $scheduleTab;
};
/**
* Get the currently active (open) Tab object
*
* @returns {Tab} the currently active tab
*/
mod.getActiveTab = function() {
return $activeTab;
};
/**
* Given a tab id, get the corresponding Tab object
*
* @param {int|string} id the tab or object ID of the Tab to retrieve
* @returns {Tab|undefined} the Tab object with the given ID, or undefined
* if no Tab with the given ID exists
*/
mod.get = function(id) {
return $.isNumeric(id) ? $openTabs[$tabMap[id]] : $openTabs[id];
};
/**
* Adjust the margins on the right-hand pane when we have multiple rows of tabs
*/
mod.onResize = function() {
var h = $(".panel-header .nav").height();
$(".pl-content").css("margin-top", h + 5); // 8px extra for padding
$("#show_builder_table_wrapper").css("top", h + 5);
};
/**
* Expose the Tab object so it can be subclassed
*
* @type {Function}
*/
mod.Tab = Tab;
return AIRTIME;
}(AIRTIME || {}));
$(document).ready(function() {
var sb = $("#show_builder");
// Add text scrolling to tab names
sb.addTitles(".tab-name");
sb.find(".nav.nav-tabs").sortable({
containment: "parent",
distance: 25
});
// Initialize the ScheduleTab
AIRTIME.tabs.initScheduleTab();
});
$(window).resize(AIRTIME.tabs.onResize);

View file

@ -0,0 +1,79 @@
function generatePartitions(partitions){
var rowTemplate =
'<tr class="partition-info">'+
'<td><span class="strong">'+$.i18n._("Disk")+' #%s</span>'+
'<ul id="watched-dir-list-%s">'+
'</ul>'+
'</td>'+
'<td>%sGB of %sGB</td>'+
'<td colspan="3">'+
'<div class="big">'+
'<div class="diskspace" style="width:%s%%;">'+
'</div>'+
'</div>'+
'<div>%s%% '+$.i18n._("in use")+'</div>'+
'</td>'+
'</tr>';
$(".partition-info").remove();
var lastElement = $('#partitions');
for (var i=0; i<partitions.length; i++){
var spaceUsed = partitions[i].totalSpace-partitions[i].totalFreeSpace;
var totalSpace = partitions[i].totalSpace;
var percUsed = sprintf("%01.1f", spaceUsed/totalSpace*100);
var spaceUsedGb = sprintf("%01.1f", spaceUsed/Math.pow(2, 30));
var totalSpaceGb = sprintf("%01.1f", totalSpace/Math.pow(2, 30));
var row = sprintf(rowTemplate, i+1, i, spaceUsedGb, totalSpaceGb, percUsed, percUsed);
var tr = $(row);
lastElement.after(tr);
if (partitions[i].dirs){
var watched_dirs_ul = $('#watched-dir-list-'+i);
for (var j=0; j<partitions[i].dirs.length; j++){
watched_dirs_ul.append('<li>'+partitions[i].dirs[j]+'</li>');
}
}
lastElement = tr;
}
}
function success(data, textStatus, jqXHR){
var services = data.status.services;
for (var key in services) {
var s = services[key];
if (s) {
var children = $("#"+s.name).children();
$(children[0]).text(s.name);
var status_class = "not-available-icon";
if (s.status == 0){
status_class = "checked-icon";
} else if (s.status == 1) {
status_class = "warning-icon";
}
$($(children[1]).children()[0]).attr("class", status_class);
$(children[2]).text(sprintf('%(days)sd %(hours)sh %(minutes)sm %(seconds)ss', convertSecondsToDaysHoursMinutesSeconds(s.uptime_seconds)));
$(children[3]).text(s.cpu_perc);
$(children[4]).text(sprintf('%01.1fMB (%s)', parseInt(s.memory_kb)/1000, s.memory_perc));
}
}
if (data.status.partitions){
generatePartitions(data.status.partitions);
}
setTimeout(function(){updateStatus(false);}, 5000);
}
function updateStatus(getDiskInfo){
$.getJSON( baseUrl+"api/status/format/json/diskinfo/"+getDiskInfo, null, success);
}
$(document).ready(function() {
updateStatus(true);
});

View file

@ -0,0 +1,151 @@
function populateForm(entries){
$('.errors').remove();
$('.success').remove();
$('#tracktype_id').val(entries.id);
$('#code').val(entries.code);
$('#type_name').val(entries.type_name);
$('#description').val(entries.description);
if (entries.visibility) {
var visibility_value = 1;
} else {
var visibility_value = 0;
}
$('#visibility').val(visibility_value);
if (entries.id.length != 0){
$('#code').attr('readonly', 'readonly');
} else {
$('#code').removeAttr('readonly');
}
}
function rowClickCallback(row_id){
$.ajax({ url: baseUrl+'Tracktype/get-tracktype-data/id/'+ row_id +'/format/json', dataType:"json", success:function(data){
populateForm(data.entries);
$("#tracktype_details").css("visibility", "visible");
}});
}
function removeTracktypeCallback(row_id, nRow) {
if (confirm($.i18n._("Are you sure you want to delete this tracktype?"))) {
$.ajax({
url: baseUrl + 'Tracktype/remove-tracktype/id/' + row_id + '/format/json',
dataType: "text",
success: function (data) {
var o = $('#tracktypes_datatable').dataTable().fnDeleteRow(nRow);
}
});
}
}
function rowCallback( nRow, aData, iDisplayIndex ){
$(nRow).click(function(){rowClickCallback(aData['id'])});
if( aData['delete'] != "self"){
$('td:eq(4)', nRow).append( '<span class="ui-icon ui-icon-closethick"></span>').children('span').click(function(e){e.stopPropagation(); removeTracktypeCallback(aData['id'], nRow)});
}else{
$('td:eq(4)', nRow).empty().append( '<span class="ui-icon ui-icon-closethick"></span>').children('span').click(function(e){e.stopPropagation(); alert("Can't delete yourself!")});
}
if ( aData['visibility'] == "1" ) {
$('td:eq(3)', nRow).html( $.i18n._('Enabled') );
} else {
$('td:eq(3)', nRow).html( $.i18n._('Disabled') );
}
return nRow;
}
function populateTracktypeTable() {
var dt = $('#tracktypes_datatable');
dt.dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": baseUrl+"Tracktype/get-tracktype-data-table-info/format/json",
"fnServerData": function ( sSource, aoData, fnCallback ) {
$.ajax( {
"dataType": 'json',
"type": "POST",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
},
"fnRowCallback": rowCallback,
"aoColumns": [
/* Id */ { "sName": "id", "bSearchable": false, "bVisible": false, "mDataProp": "id" },
/* code */ { "sName": "code", "mDataProp": "code" },
/* type_name */ { "sName": "type_name", "mDataProp": "type_name" },
/* description */ { "sName": "description", "mDataProp": "description" },
/* visibility */ { "sName": "visibility", "bSearchable": false, "mDataProp": "visibility" },
/* del button */ { "sName": "null as delete", "bSearchable": false, "bSortable": false, "mDataProp": "delete"}
],
"bJQueryUI": true,
"bAutoWidth": false,
"bLengthChange": false,
"oLanguage": getDatatablesStrings({
"sEmptyTable": $.i18n._("No track types were found."),
"sEmptyTable": $.i18n._("No track types found"),
"sZeroRecords": $.i18n._("No matching track types found"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ track types"),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 track types"),
"sInfoFiltered": $.i18n._("(filtered from _MAX_ total track types)"),
}),
"sDom": '<"H"lf<"dt-process-rel"r>><"#tracktype_list_inner_wrapper"t><"F"ip>'
});
}
function sizeFormElements() {
$("dt[id$='label']").addClass('tracktype-form-label');
$("dd[id$='element']").addClass('tracktype-form-element');
}
function initTracktypeData() {
var visibility = $('#visibility');
var table = $("#tracktypes_datable");//.DataTable();
$('.datatable tbody').on( 'click', 'tr', function () {
$(this).parent().find('tr.selected').removeClass('selected');
$(this).addClass('selected');
} );
$('#button').click( function () {
table.row('.selected').remove().draw( false );
} );
var newTracktype = {code:"", type_name:"", description:"", visibility:"1", id:""};
$('#add_tracktype_button').live('click', function(){
populateForm(newTracktype);
$("#tracktype_details").css("visibility", "visible");
});
}
$(document).ready(function() {
populateTracktypeTable();
initTracktypeData();
$('#save_tracktype').live('click', function(){
var data = $('#tracktype_form').serialize();
var url = baseUrl+'Tracktype/add-tracktype';
$.post(url, {format: "json", data: data}, function(json){
if (json.valid === "true") {
$('#content').empty().append(json.html);
populateTracktypeTable();
init(); // Reinitialize
} else {
//if form is invalid we only need to redraw the form
$('#tracktype_form').empty().append($(json.html).find('#tracktype_form').children());
}
setTimeout(removeSuccessMsg, 5000);
sizeFormElements();
});
});
sizeFormElements();
});

View file

@ -0,0 +1,258 @@
function populateForm(entries){
//$('#user_details').show();
$('.errors').remove();
$('.success').remove();
if (entries.type === 'S')
{
$("#user_details").hide();
$("#user_details_superadmin_message").show();
$('#type').attr('disabled', '1');
} else {
$("#user_details").show();
$("#user_details_superadmin_message").hide();
$('#type').removeAttr('disabled');
}
$('#user_id').val(entries.id);
$('#login').val(entries.login);
$('#first_name').val(entries.first_name);
$('#last_name').val(entries.last_name);
$('#type').val(entries.type);
$('#email').val(entries.email);
$('#cell_phone').val(entries.cell_phone);
$('#skype').val(entries.skype_contact);
$('#jabber').val(entries.jabber_contact);
if (entries.id.length != 0){
$('#login').attr('readonly', 'readonly');
$('#password').val("xxxxxx");
$('#passwordVerify').val("xxxxxx");
} else {
$('#login').removeAttr('readonly');
$('#password').val("");
$('#passwordVerify').val("");
}
}
function rowClickCallback(row_id){
$.ajax({ url: baseUrl+'User/get-user-data/id/'+ row_id +'/format/json', dataType:"json", success:function(data){
populateForm(data.entries);
$("#user_details").css("visibility", "visible");
}});
}
function removeUserCallback(row_id, nRow) {
if (confirm($.i18n._("Are you sure you want to delete this user?"))) {
$.ajax({
url: baseUrl + 'User/remove-user/id/' + row_id + '/format/json',
dataType: "text",
success: function (data) {
var o = $('#users_datatable').dataTable().fnDeleteRow(nRow);
}
});
}
}
function rowCallback( nRow, aData, iDisplayIndex ){
$(nRow).click(function(){rowClickCallback(aData['id'])});
if( aData['delete'] != "self"){
$('td:eq(4)', nRow).append( '<span class="ui-icon ui-icon-closethick"></span>').children('span').click(function(e){e.stopPropagation(); removeUserCallback(aData['id'], nRow)});
}else{
$('td:eq(4)', nRow).empty().append( '<span class="ui-icon ui-icon-closethick"></span>').children('span').click(function(e){e.stopPropagation(); alert($.i18n._("Can't delete yourself!"))});
}
if ( aData['type'] == "A" )
{
$('td:eq(3)', nRow).html( $.i18n._('Admin') );
} else if ( aData['type'] == "H" )
{
$('td:eq(3)', nRow).html( $.i18n._('DJ') );
} else if ( aData['type'] == "G" )
{
$('td:eq(3)', nRow).html( $.i18n._('Guest') );
} else if ( aData['type'] == "P" )
{
$('td:eq(3)', nRow).html( $.i18n._('Program Manager') );
} else if ( aData['type'] == "S" )
{
$('td:eq(3)', nRow).html( $.i18n._('Super Admin') );
$('td:eq(4)', nRow).html(""); //Disable deleting the super admin
}
return nRow;
}
function populateUserTable() {
var dt = $('#users_datatable');
dt.dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": baseUrl+"User/get-user-data-table-info/format/json",
"fnServerData": function ( sSource, aoData, fnCallback ) {
$.ajax( {
"dataType": 'json',
"type": "POST",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
},
"fnRowCallback": rowCallback,
"aoColumns": [
/* Id */ { "sName": "id", "bSearchable": false, "bVisible": false, "mDataProp": "id" },
/* user name */ { "sName": "login", "mDataProp": "login" },
/* first name */ { "sName": "first_name", "mDataProp": "first_name" },
/* last name */ { "sName": "last_name", "mDataProp": "last_name" },
/* user type */ { "sName": "type", "bSearchable": false, "mDataProp": "type" },
/* del button */ { "sName": "null as delete", "bSearchable": false, "bSortable": false, "mDataProp": "delete"}
],
"bJQueryUI": true,
"bAutoWidth": false,
"bLengthChange": false,
"oLanguage": getDatatablesStrings({
"sEmptyTable": $.i18n._("No users were found."),
"sEmptyTable": $.i18n._("No users found"),
"sZeroRecords": $.i18n._("No matching users found"),
"sInfo": $.i18n._("Showing _START_ to _END_ of _TOTAL_ users"),
"sInfoEmpty": $.i18n._("Showing 0 to 0 of 0 users"),
"sInfoFiltered": $.i18n._("(filtered from _MAX_ total users)"),
}),
"sDom": '<"H"lf<"dt-process-rel"r>><"#user_list_inner_wrapper"t><"F"ip>'
});
}
function sizeFormElements() {
$("dt[id$='label']").addClass('user-form-label');
$("dd[id$='element']").addClass('user-form-element');
}
function assignUserRightsToUserTypes() {
//assign user-rights and id to each user type option so we can
//display user rights for each with tipsy tooltip
$.each($('#type').children(), function(i, opt) {
switch ($(this).val()) {
case 'G':
$(this).attr('id', 'user-type-G');
$(this).attr('user-rights',
$.i18n._('Guests can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View show content')
);
break;
case 'H':
$(this).attr('id', 'user-type-H');
$(this).attr('user-rights',
$.i18n._('DJs can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View show content')+'<br>'+
$.i18n._('Manage assigned show content')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage their own library content')
);
break;
case 'P':
$(this).attr('id', 'user-type-P');
$(this).attr('user-rights',
$.i18n._('Program Managers can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View and manage show content')+'<br>'+
$.i18n._('Schedule shows')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage all library content')
);
break;
case 'A':
$(this).attr('id', 'user-type-A');
$(this).attr('user-rights',
$.i18n._('Admins can do the following:')+'<br><br>'+
$.i18n._('Manage preferences')+'<br>'+
$.i18n._('Manage users')+'<br>'+
$.i18n._('Manage watched folders')+'<br>'+
$.i18n._('Send support feedback')+'<br>'+
$.i18n._('View system status')+'<br>'+
$.i18n._('Access playout history')+'<br>'+
$.i18n._('View listener stats')+'<br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View and manage show content')+'<br>'+
$.i18n._('Schedule shows')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage all library content')
);
break;
}
});
}
function initUserData() {
var type = $('#type');
type.live("change", function(){
//when the title changes on live tipsy tooltips the changes take
//affect the next time tipsy is shown so we need to hide and re-show it
$(this).tipsy('hide').tipsy('show');
});
type.tipsy({
gravity: 'w',
html: true,
opacity: 0.9,
trigger: 'manual',
live: true,
title: function() {
return $('#user-type-'+$(this).val()).attr('user-rights');
}
});
var table = $("#users_datable");//.DataTable();
$('.datatable tbody').on( 'click', 'tr', function () {
$(this).parent().find('tr.selected').removeClass('selected');
$(this).addClass('selected');
} );
$('#button').click( function () {
table.row('.selected').remove().draw( false );
} );
type.tipsy('show');
var newUser = {login:"", first_name:"", last_name:"", type:"G", id:""};
$('#add_user_button').live('click', function(){
populateForm(newUser);
$("#user_details").css("visibility", "visible");
});
}
$(document).ready(function() {
populateUserTable();
assignUserRightsToUserTypes();
initUserData();
$('#save_user').live('click', function(){
var data = $('#user_form').serialize();
var url = baseUrl+'User/add-user';
$.post(url, {format: "json", data: data}, function(json){
if (json.valid === "true") {
$('#content').empty().append(json.html);
populateUserTable();
assignUserRightsToUserTypes();
init(); // Reinitialize
} else {
//if form is invalid we only need to redraw the form
$('#user_form').empty().append($(json.html).find('#user_form').children());
$('#password').val("");
$('#passwordVerify').val("");
}
setTimeout(removeSuccessMsg, 5000);
sizeFormElements();
});
});
sizeFormElements();
});

View file

@ -0,0 +1,76 @@
var AIRTIME = (function(AIRTIME){
var mod;
if (AIRTIME.utilities === undefined) {
AIRTIME.utilities = {};
}
mod = AIRTIME.utilities;
mod.findViewportDimensions = function() {
var viewportwidth,
viewportheight;
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
}
// IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document)
else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight;
}
// older versions of IE
else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
return {
width: viewportwidth,
height: viewportheight
};
};
/*
* Returns an object containing a unix timestamp in seconds for the start/end range
*
* @return Object {"start", "end", "range"}
*/
mod.fnGetScheduleRange = function(dateStartId, timeStartId, dateEndId, timeEndId) {
var start,
end,
time;
start = $(dateStartId).val();
start = start === "" ? null : start;
time = $(timeStartId).val();
time = time === "" ? "00:00" : time;
if (start) {
start = start + " " + time;
}
end = $(dateEndId).val();
end = end === "" ? null : end;
time = $(timeEndId).val();
time = time === "" ? "00:00" : time;
if (end) {
end = end + " " + time;
}
return {
start: start,
end: end
};
};
return AIRTIME;
}(AIRTIME || {}));

View file

@ -0,0 +1,32 @@
/**
* Created by asantoni on 11/09/15.
*/
$(document).ready(function() {
var aoColumns = [
/* Artwork */ { "sTitle" : $.i18n._("Artwork") , "mDataProp" : "artwork" , "bVisible" : false , "sClass" : "library_artwork" , "sWidth" : "150px" },
/* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" },
/* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" },
/* Upload Time */ { "sTitle" : $.i18n._("Uploaded") , "mDataProp" : "utime" , "bVisible" : false , "sClass" : "library_upload_time" , "sWidth" : "155px" },
/* Website */ { "sTitle" : $.i18n._("Website") , "mDataProp" : "info_url" , "bVisible" : false , "sClass" : "library_url" , "sWidth" : "150px" },
/* Year */ { "sTitle" : $.i18n._("Year") , "mDataProp" : "year" , "bVisible" : false , "sClass" : "library_year" , "sWidth" : "60px" },
];
var ajaxSourceURL = baseUrl+"rest/media";
var myToolbarButtons = AIRTIME.widgets.Table.getStandardToolbarButtons();
myToolbarButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.NEW].eventHandlers.click = function(e) { alert('New!'); };
myToolbarButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.EDIT].eventHandlers.click = function(e) { alert('Edit!'); };
myToolbarButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.DELETE].eventHandlers.click = function(e) { alert('Delete!'); };
//Set up the div with id "example-table" as a datatable.
var table = new AIRTIME.widgets.Table(
$('#example-table'), //DOM node to create the table inside.
true, //Enable item selection
myToolbarButtons, //Toolbar buttons
{ //Datatables overrides.
'aoColumns' : aoColumns,
'sAjaxSource' : ajaxSourceURL
});
});

View file

@ -0,0 +1,541 @@
/**
* Created by asantoni on 11/09/15.
*/
var AIRTIME = (function(AIRTIME) {
//Module initialization
if (AIRTIME.widgets === undefined) {
AIRTIME.widgets = {};
}
//Table widget constructor
/**
*
*
* @param wrapperDOMNode
* @param {boolean} bItemSelection
* @param {Object} toolbarButtons
* @param {Object} dataTablesOptions
* @param {Object} [emptyPlaceholder]
* @param {string} emptyPlaceholder.html
* @param {string} emptyPlaceholder.iconClass
*
* @returns {Table}
* @constructor
*/
var Table = function(wrapperDOMNode, bItemSelection, toolbarButtons, dataTablesOptions, emptyPlaceholder) {
var self = this;
self.HUGE_INT = Math.pow(2, 53) - 1;
//Constants and enumerations
self.SELECTION_MODE = {
SINGLE : 0,
MULTI_SHIFT : 1,
MULTI_CTRL : 2
};
//Member variables
self._datatable = null;
self._selectedRows = []; //An array containing the underlying objects for each selected row. (Easy to use!)
//self._selectedRowVisualIdxMap = []; //A map of the visual index of a selected rows onto the actual row data.
self._selectedRowVisualIdxMin = self.HUGE_INT;
self._selectedRowVisualIdxMax = -1;
self._$wrapperDOMNode = null;
self._toolbarButtons = null;
//Save some of the constructor parameters
self._$wrapperDOMNode = $(wrapperDOMNode);
self._toolbarButtons = toolbarButtons;
self._emptyPlaceholder = emptyPlaceholder;
// Exclude the leftmost column if we're implementing item selection
self._colVisExcludeColumns = bItemSelection ? [0] : [];
//Finish initialization of the datatable since everything is declared by now.
// If selection is enabled, add in the checkbox column.
if (bItemSelection) {
dataTablesOptions["aoColumns"].unshift(
/* Checkbox */ { "sTitle" : "", "mData" : self._datatablesCheckboxDataDelegate.bind(this), "bSortable" : false , "bSearchable" : false , "sWidth" : "24px" , "sClass" : "airtime_table_checkbox" }
);
}
var options = {
"aoColumns": [
/* Title */ { "sTitle" : $.i18n._("Make sure to override me") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" },
],
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": baseUrl+"rest/media", //Override me
"sAjaxDataProp": "aaData",
"bScrollCollapse": false,
"deferLoading" : 1, //0 tells it there's zero elements loaded and disables the automatic AJAX. We don't want to load until after we bind all our event handlers, to prevent a race condition with the "init" event callback.
"sPaginationType": "full_numbers",
"bJQueryUI": true,
"bAutoWidth": false,
"aaSorting": [],
"iDisplayLength": 25,
"aLengthMenu": [25, 50, 100],
"oLanguage" : getDatatablesStrings({
"sEmptyTable": $.i18n._(""),
"sZeroRecords": $.i18n._("No matching results found.")
}),
"oColVis": {
"sAlign": "right",
"aiExclude": self._colVisExcludeColumns,
"buttonText": $.i18n._("Columns"),
"iOverlayFade": 0
},
// z = ColResize, R = ColReorder, C = ColVis
"sDom": 'Rf<"dt-process-rel"r><"H"<"table_toolbar"C>><"dataTables_scrolling"t<".empty_placeholder"<".empty_placeholder_image"><".empty_placeholder_text">>><"F"lip>>',
"fnPreDrawCallback": function () {
$("#draggingContainer").remove();
},
"fnServerData": self._fetchData.bind(self),
//"fnInitComplete" : function() { self._setupEventHandlers(bItemSelection) }
"fnDrawCallback": function () {
self.clearSelection();
}
};
//Override any options with those passed in as arguments to this constructor.
for (var key in dataTablesOptions)
{
options[key] = dataTablesOptions[key];
}
if (options.fnCreatedRow) {
options.fnCreatedRow = options.fnCreatedRow.bind(self);
}
if (options.fnDrawCallback) {
options.fnDrawCallback = options.fnDrawCallback.bind(self);
}
self._datatable = self._$wrapperDOMNode.dataTable(options);
// self._datatable.fnDraw(); //Load the AJAX data now that our event handlers have been bound.
self._setupEventHandlers(bItemSelection);
//return self._datatable;
return self;
};
Table.prototype.assignDblClickHandler = function(fn) {
$(this._datatable, 'tbody tr').on('dblclick', this._SELECTORS.SELECTION_TABLE_ROW, fn);
};
/* Set up global event handlers for the datatable.
* @param bItemSelection Whether or not row selection behaviour should be enabled for this widget.
* */
Table.prototype._setupEventHandlers = function(bItemSelection) {
var self = this;
/** This table row event handler is created once and catches events for any row. (It's less resource intensive
* than having a per-row callback...)
*/
if (bItemSelection) {
$(self._datatable, 'tbody tr').on('click contextmenu', self._SELECTORS.SELECTION_TABLE_ROW, function (e) {
var aData = self._datatable.fnGetData(this);
var iDisplayIndex = $(this).index(); // The index of the row in the current page in the table.
var nRow = this;
e.stopPropagation();
e.preventDefault();
document.getSelection().removeAllRanges();
var selectionMode = self.SELECTION_MODE.SINGLE;
if (e.shiftKey) {
selectionMode = self.SELECTION_MODE.MULTI_SHIFT;
} else if (e.ctrlKey) {
selectionMode = self.SELECTION_MODE.MULTI_CTRL;
}
if (e.button == 2) {
selectionMode = self.SELECTION_MODE.SINGLE;
}
self.selectRow(nRow, aData, selectionMode, iDisplayIndex);
});
$(self._datatable, 'tbody tr').on('click', self._SELECTORS.SELECTION_CHECKBOX, function(e) {
$this = $(this);
var iVisualRowIdx = $this.parent().index();
var aData = self._datatable.fnGetData(iVisualRowIdx);
var selectionMode = self.SELECTION_MODE.MULTI_CTRL; //Behaviour for checkboxes.
if (e.shiftKey) {
selectionMode = self.SELECTION_MODE.MULTI_SHIFT;
}
self.selectRow($this.parent(), aData, selectionMode, iVisualRowIdx); //Always multiselect for checkboxes
e.stopPropagation();
return true;
});
// Clear selection when switching pages
$(self._datatable).on('page', function () {
self.clearSelection();
});
}
// On filter, display the number of total and filtered results in the search bar
$(self._datatable).on('filter', function() {
var dt = self._datatable, f = dt.closest(".dataTables_wrapper").find(".filter-message"),
totalRecords = dt.fnSettings().fnRecordsTotal(),
totalDisplayRecords = dt.fnSettings().fnRecordsDisplay();
if (f.length === 0) {
var el = document.createElement("span");
el.setAttribute("class", "filter-message");
f = dt.closest(".dataTables_wrapper").find(".dataTables_filter").append(el).find(".filter-message");
}
f.text(totalRecords > totalDisplayRecords ?
$.i18n._("Filtering out ") + (totalRecords - totalDisplayRecords)
+ $.i18n._(" of ") + totalRecords
+ $.i18n._(" records") : ""
);
dt.closest(".dataTables_wrapper").find('.dataTables_filter input[type="text"]')
.css('padding-right', f.outerWidth());
});
//Since this function is already called when the datatables initialization is complete, we know the DOM
//structure for the datatable exists and can just proceed to setup the toolbar DOM elements now.
self._setupToolbarButtons(self._toolbarButtons);
};
/**
* Member functions
*
*/
/** Populate the toolbar with buttons.
*
* @param buttons A list of objects which contain button definitions. See self.TOOLBAR_BUTTON_ROLES for an example, or use getStandardToolbarButtons() to get a list of them.
* @private
*/
Table.prototype._setupToolbarButtons = function(buttons) {
var self = this;
var $menu = self._$wrapperDOMNode.parent().parent().find("div.table_toolbar");
$menu.addClass("btn-toolbar");
//Create the toolbar buttons.
$.each(buttons, function(idx, btn) {
var buttonElement = self._createToolbarButton(btn.title, btn.iconClass, btn.extraBtnClass, btn.elementId);
$menu.append(buttonElement);
btn.element = buttonElement; //Save this guy in case you need it later.
//Bind event handlers to each button
$.each(btn.eventHandlers, function(eventName, eventCallback) {
$(buttonElement).on(eventName, function () {
if ($(buttonElement).find("button").is(':disabled')) { return; }
eventCallback();
});
});
});
self._checkToolbarButtons();
};
/**
* Check each of the toolbar buttons for the table and disable them if their constraints are invalid.
*
* Passes current Table object context to function calls.
*/
Table.prototype._checkToolbarButtons = function () {
var self = this;
$.each(self._toolbarButtons, function (idx, btn) {
var btnNode = $(btn.element).find("button").get(0);
btnNode.disabled = btn.disabled = !btn.validateConstraints.call(self);
});
};
/** Create the DOM element for a toolbar button and return it. */
Table.prototype._createToolbarButton = function(title, iconClass, extraBtnClass, elementId) {
if (!iconClass) {
iconClass = 'icon-plus';
}
// var title = $.i18n._('Delete');
var outerDiv = document.createElement("div");
outerDiv.className = 'btn-group';
outerDiv.title = title;
var innerButton = document.createElement("button");
//innerButton.className = 'btn btn-small ' + extraBtnClass;
innerButton.className = 'btn ' + extraBtnClass;
innerButton.id = elementId;
var innerIcon = document.createElement("i");
innerIcon.className = 'icon-white ' + iconClass;
var innerTextSpan = document.createElement('span');
var innerText = document.createTextNode(title);
innerTextSpan.appendChild(innerText);
innerButton.appendChild(innerIcon);
innerButton.appendChild(innerTextSpan);
outerDiv.appendChild(innerButton);
/* Here's an example of what the button HTML should look like:
"<div class='btn-group' title=" + $.i18n._('Delete') + ">" +
"<button class='btn btn-small btn-danger' id='sb-trash'>" +
"<i class='icon-white icon-trash'></i>" +
"<span>" + $.i18n._('Delete') + "</span>" +
"</button>" +
"</div>"*/
return outerDiv;
};
Table.prototype.clearSelection = function() {
this._selectedRows = [];
//self._selectedRowVisualIdxMap = [];
this._selectedRowVisualIdxMin = self.HUGE_INT;
this._selectedRowVisualIdxMax = -1;
this._$wrapperDOMNode.find('.selected').removeClass('selected');
this._$wrapperDOMNode.find(this._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', false);
this._checkToolbarButtons();
};
/** @param nRow is a tr DOM node (non-jQuery)
* @param aData is an array containing the raw data for the row. Can be null if you don't have it.
* @param selectionMode is an SELECT_MODE enum. Specify what selection mode you want to use for this action.
* @param iVisualRowIdx is an integer which corresponds to the index of the clicked row, as it appears to the user.
* eg. The 5th row in the table will have an iVisualRowIdx of 4 (0-based).
*/
Table.prototype.selectRow = function(nRow, aData, selectionMode, iVisualRowIdx) {
var self = this;
//Default to single item selection.
if (selectionMode == undefined) {
selectionMode = self.SELECTION_MODE.SINGLE;
}
var $nRow = $(nRow);
//Regular single left-click mode
if (selectionMode == self.SELECTION_MODE.SINGLE) {
self.clearSelection();
self._selectedRows.push(aData);
self._selectedRowVisualIdxMin = iVisualRowIdx;
self._selectedRowVisualIdxMax = iVisualRowIdx;
//self._selectedRowVisualIdxMap[iVisualRowIdx] = aData;
$nRow.addClass('selected');
$nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
}
//Ctrl-click multi row selection mode
else if (selectionMode == self.SELECTION_MODE.MULTI_CTRL) {
var foundAtIdx = $.inArray(aData, self._selectedRows);
//console.log('checkbox mouse', iVisualRowIdx, foundAtIdx);
//If the clicked row is already selected, deselect it.
if (foundAtIdx >= 0 && self._selectedRows.length >= 1) {
self._selectedRows.splice(foundAtIdx, 1);
$nRow.removeClass('selected');
$nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', false);
}
else {
self._selectedRows.push(aData);
self._selectedRowVisualIdxMin = iVisualRowIdx;
self._selectedRowVisualIdxMax = iVisualRowIdx;
$nRow.addClass('selected');
$nRow.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
}
}
//Shift-click multi row selection mode
else if (selectionMode == self.SELECTION_MODE.MULTI_SHIFT) {
//If there's no rows selected, just behave like single selection.
if (self._selectedRows.length == 0) {
return self.selectRow(nRow, aData, self.SELECTION_MODE.SINGLE, iVisualRowIdx);
}
if (iVisualRowIdx > self._selectedRowVisualIdxMax) {
self._selectedRowVisualIdxMax = iVisualRowIdx;
}
if (iVisualRowIdx < self._selectedRowVisualIdxMin) {
self._selectedRowVisualIdxMin = iVisualRowIdx;
}
var selectionStartRowIdx = Math.min(iVisualRowIdx, self._selectedRowVisualIdxMin);
var selectionEndRowIdx = Math.min(iVisualRowIdx, self._selectedRowVisualIdxMax);
//We can assume there's at least 1 row already selected now.
var allRows = self._datatable.fnGetData();
self._selectedRows = [];
for (var i = self._selectedRowVisualIdxMin; i <= self._selectedRowVisualIdxMax; i++)
{
self._selectedRows.push(allRows[i]);
$row = $($nRow.parent().children()[i]);
$row.addClass('selected');
$row.find(self._SELECTORS.SELECTION_CHECKBOX).find('input').attr('checked', true);
}
}
else {
console.log("Unimplemented selection mode");
}
self._checkToolbarButtons();
};
Table.prototype.getSelectedRows = function() {
return this._selectedRows;
};
Table.prototype.getEmptyPlaceholder = function () {
return this._emptyPlaceholder;
};
Table.prototype._handleAjaxError = function(r) {
// If the request was denied due to permissioning
if (r.status === 403) {
$(".dt-process-rel").hide();
$('.empty_placeholder_text').text($.i18n._("You don't have permission to view this resource."));
$('.empty_placeholder').show();
}
};
/** Grab data from a REST API and format so that DataTables can display it.
* This is the DataTables REST adapter function, basically.
* */
Table.prototype._fetchData = function ( sSource, aoData, fnCallback, oSettings )
{
var self = this;
var echo = aoData[0].value; //Datatables state tracking. Must be included.
var sortColName = "";
var sortDir = "";
var search = self._$wrapperDOMNode.closest(".dataTables_wrapper").find(".dataTables_filter").find("input").val();
if (oSettings.aaSorting.length > 0) {
var sortColIdx = oSettings.aaSorting[0][0];
sortColName = oSettings.aoColumns[sortColIdx].mDataProp;
sortDir = oSettings.aaSorting[0][1].toUpperCase();
}
// FIXME: We should probably just be sending aoData back here..?
$.ajax({
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": {
"limit": oSettings._iDisplayLength,
"offset": oSettings._iDisplayStart,
"sort": sortColName,
"sort_dir": sortDir,
"search": search
},
"success": function (json, textStatus, jqXHR) {
var rawResponseJSON = json;
json = [];
json.aaData = rawResponseJSON;
json.iTotalRecords = jqXHR.getResponseHeader('X-TOTAL-COUNT');
json.iTotalDisplayRecords = json.iTotalRecords;
json.sEcho = echo;
//Pass it along to datatables.
fnCallback(json);
},
"error": self._handleAjaxError
});
};
Table.prototype._datatablesCheckboxDataDelegate = function(rowData, callType, dataToSave) {
if (callType == undefined) {
//Supposed to return the raw data for the type here.
return null;
} else if (callType == 'display') {
return "<input type='checkbox'>";
} else if (callType == 'sort') {
return null;
} else if (callType == 'type') {
return "input";
} else if (callType == 'set') {
//The data to set is in dataToSave.
return;
} else if (callType == 'filter') {
return null;
}
//For all other calls, just return the data as this:
return "check";
};
//Accessors / Mutators
Table.prototype.getDatatable = function() {
return this._datatable;
};
//Static initializers / Class variables
Table.prototype._SELECTORS = Object.freeze({
SELECTION_CHECKBOX: ".airtime_table_checkbox",
SELECTION_TABLE_ROW: "tr"
});
Table.TOOLBAR_BUTTON_ROLES = {
NEW : 0,
EDIT : 1,
DELETE : 2
};
Object.freeze(Table.TOOLBAR_BUTTON_ROLES);
//Set of standard buttons. Use getStandardToolbarButtons() to grab these and pass them to the init() function.
Table._STANDARD_TOOLBAR_BUTTONS = {};
Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.NEW] = {
title : $.i18n._('New'),
iconClass : "icon-plus",
extraBtnClass : "btn-small btn-new",
elementId : '',
eventHandlers : {},
validateConstraints: function () { return true; }
};
Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.EDIT] = {
title : $.i18n._('Edit'),
iconClass : "icon-pencil",
extraBtnClass : "btn-small",
elementId : '',
eventHandlers : {},
validateConstraints: function () { return true; }
};
Table._STANDARD_TOOLBAR_BUTTONS[Table.TOOLBAR_BUTTON_ROLES.DELETE] = {
title : $.i18n._('Delete'),
iconClass : "icon-trash",
extraBtnClass : "btn-small btn-danger",
elementId : '',
eventHandlers : {},
validateConstraints: function () { return true; }
};
Object.freeze(Table._STANDARD_TOOLBAR_BUTTONS);
//Static method
Table.getStandardToolbarButtons = function() {
//Return a deep copy
return jQuery.extend(true, {}, Table._STANDARD_TOOLBAR_BUTTONS);
};
//Add Table to the widgets namespace
AIRTIME.widgets.Table = Table;
return AIRTIME;
}(AIRTIME || {}));