builder.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296
  1. var AIRTIME = (function(AIRTIME){
  2. var mod,
  3. oSchedTable,
  4. SB_SELECTED_CLASS = "sb-selected",
  5. CURSOR_SELECTED_CLASS = "cursor-selected-row",
  6. NOW_PLAYING_CLASS = "sb-now-playing",
  7. $sbContent,
  8. $sbTable,
  9. $toolbar,
  10. $ul,
  11. $lib,
  12. cursors = [],
  13. cursorIds = [],
  14. showInstanceIds = [],
  15. headerFooter = [];
  16. if (AIRTIME.showbuilder === undefined) {
  17. AIRTIME.showbuilder = {};
  18. }
  19. mod = AIRTIME.showbuilder;
  20. function checkError(json) {
  21. if (json.error !== undefined) {
  22. alert(json.error);
  23. }
  24. }
  25. mod.timeout = undefined;
  26. mod.timestamp = -1;
  27. mod.showInstances = [];
  28. mod.resetTimestamp = function() {
  29. mod.timestamp = -1;
  30. };
  31. mod.setTimestamp = function(timestamp) {
  32. mod.timestamp = timestamp;
  33. };
  34. mod.updateCalendarStatusIcon = function(json) {
  35. //make sure we are only executing this code on the calendar view, not
  36. //the Now Playing view.
  37. if (window.location.pathname.toLowerCase().indexOf("schedule") < 0) {
  38. return;
  39. }
  40. var instance_id = json.schedule[0].instance;
  41. var lastElem = json.schedule[json.schedule.length-1];
  42. var $elem = $("#fc-show-instance-"+instance_id);
  43. //if the show is linked, then replace $elem to reference all linked
  44. //instances
  45. if ($elem.data("show-linked") == "1") {
  46. var show_id = $elem.data("show-id");
  47. $elem = $('*[data-show-id="'+show_id+'"]');
  48. }
  49. $elem.find(".show-empty, .show-partial-filled").remove();
  50. if (json.schedule[1].empty) {
  51. $elem
  52. .find(".fc-event-inner")
  53. .append('<span id="'+instance_id+'" title="'+$.i18n._("Show is empty")+'" class="small-icon show-empty"></span>');
  54. } else if (lastElem["fRuntime"][0] == "-") {
  55. $elem
  56. .find(".fc-event-inner")
  57. .append('<span id="'+instance_id+'" title="'+$.i18n._("Show is partially filled")+'" class="small-icon show-partial-filled"></span>');
  58. }
  59. }
  60. mod.getTimestamp = function() {
  61. if (mod.timestamp !== undefined) {
  62. return mod.timestamp;
  63. }
  64. else {
  65. return -1;
  66. }
  67. };
  68. mod.setShowInstances = function(showInstances) {
  69. mod.showInstances = showInstances;
  70. };
  71. mod.getShowInstances = function() {
  72. return mod.showInstances;
  73. };
  74. mod.refresh = function(schedId) {
  75. mod.resetTimestamp();
  76. // once a track plays out we need to check if we can update
  77. // the is_scheduled flag in cc_files
  78. if (schedId > 0) {
  79. $.post(baseUrl+"schedule/update-future-is-scheduled",
  80. {"format": "json", "schedId": schedId}, function(data) {
  81. if (data.redrawLibTable !== undefined && data.redrawLibTable) {
  82. $("#library_content").find("#library_display").dataTable().fnStandingRedraw();
  83. }
  84. });
  85. oSchedTable.fnDraw();
  86. }
  87. };
  88. mod.checkSelectButton = function() {
  89. var $selectable = $sbTable.find("tbody").find("input:checkbox");
  90. if ($selectable.length !== 0) {
  91. AIRTIME.button.enableButton("btn-group #timeline-select", false);
  92. }
  93. else {
  94. AIRTIME.button.disableButton("btn-group #timeline-select", false);
  95. }
  96. //need to check if the 'Select' button is disabled
  97. if ($(".btn-group #timeline-select").is(":disabled")) {
  98. $(".btn-group #timeline-select").removeAttr("disabled");
  99. }
  100. };
  101. mod.checkTrimButton = function() {
  102. var $over = $sbTable.find(".sb-over.sb-allowed");
  103. if ($over.length !== 0) {
  104. AIRTIME.button.enableButton("icon-cut", true);
  105. }
  106. else {
  107. AIRTIME.button.disableButton("icon-cut", true);
  108. }
  109. };
  110. mod.checkDeleteButton = function() {
  111. var $selected = $sbTable.find("tbody").find("input:checkbox").filter(":checked");
  112. if ($selected.length !== 0) {
  113. AIRTIME.button.enableButton("icon-trash", true);
  114. }
  115. else {
  116. AIRTIME.button.disableButton("icon-trash", true);
  117. }
  118. };
  119. mod.checkJumpToCurrentButton = function() {
  120. var $current = $sbTable.find("."+NOW_PLAYING_CLASS);
  121. if ($current.length !== 0) {
  122. AIRTIME.button.enableButton("icon-step-forward", true);
  123. }
  124. else {
  125. AIRTIME.button.disableButton("icon-step-forward", true);
  126. }
  127. };
  128. mod.checkCancelButton = function() {
  129. var $current = $sbTable.find(".sb-current-show"),
  130. //this user type should be refactored into a separate users module later
  131. //when there's more time and more JS will need to know user data.
  132. userType = localStorage.getItem('user-type'),
  133. canCancel = false;
  134. if ($current.length !== 0 && $current.hasClass("sb-allowed")) {
  135. canCancel = true;
  136. }
  137. else if ($current.length !== 0 && (userType === 'A' || userType === 'P')) {
  138. canCancel = true;
  139. }
  140. if (canCancel === true) {
  141. AIRTIME.button.enableButton("icon-ban-circle", true);
  142. }
  143. else {
  144. AIRTIME.button.disableButton("icon-ban-circle", true);
  145. }
  146. };
  147. mod.checkToolBarIcons = function() {
  148. //library may not be on the page.
  149. if (AIRTIME.library !== undefined) {
  150. AIRTIME.library.checkAddButton();
  151. }
  152. mod.checkSelectButton();
  153. mod.checkTrimButton();
  154. mod.checkDeleteButton();
  155. mod.checkJumpToCurrentButton();
  156. mod.checkCancelButton();
  157. };
  158. mod.selectCursor = function($el) {
  159. $el.addClass(CURSOR_SELECTED_CLASS);
  160. mod.checkToolBarIcons();
  161. };
  162. mod.removeCursor = function($el) {
  163. $el.removeClass(CURSOR_SELECTED_CLASS);
  164. mod.checkToolBarIcons();
  165. };
  166. /*
  167. * sNot is an optional string to filter selected elements by. (ex removing the currently playing item)
  168. */
  169. mod.getSelectedData = function(sNot) {
  170. var $selected = $sbTable.find("tbody").find("input:checkbox").filter(":checked").parents("tr"),
  171. aData = [],
  172. i, length,
  173. $item;
  174. if (sNot !== undefined) {
  175. $selected = $selected.not("."+sNot);
  176. }
  177. for (i = 0, length = $selected.length; i < length; i++) {
  178. $item = $($selected.get(i));
  179. aData.push($item.data('aData'));
  180. }
  181. return aData.reverse();
  182. };
  183. mod.selectAll = function () {
  184. $inputs = $sbTable.find("input:checkbox");
  185. $inputs.attr("checked", true);
  186. $trs = $inputs.parents("tr");
  187. $trs.addClass(SB_SELECTED_CLASS);
  188. mod.checkToolBarIcons();
  189. };
  190. mod.selectNone = function () {
  191. $inputs = $sbTable.find("input:checkbox");
  192. $inputs.attr("checked", false);
  193. $trs = $inputs.parents("tr");
  194. $trs.removeClass(SB_SELECTED_CLASS);
  195. mod.checkToolBarIcons();
  196. };
  197. mod.disableUI = function() {
  198. $lib.block({
  199. message: "",
  200. theme: true,
  201. applyPlatformOpacityRules: false
  202. });
  203. $sbContent.block({
  204. message: "",
  205. theme: true,
  206. applyPlatformOpacityRules: false
  207. });
  208. };
  209. mod.enableUI = function() {
  210. $lib.unblock();
  211. $sbContent.unblock();
  212. //Block UI changes the postion to relative to display the messages.
  213. $lib.css("position", "static");
  214. $sbContent.css("position", "static");
  215. };
  216. mod.fnItemCallback = function(json) {
  217. checkError(json);
  218. mod.getSelectedCursors();
  219. oSchedTable.fnDraw();
  220. mod.enableUI();
  221. $("#library_content").find("#library_display").dataTable().fnStandingRedraw();
  222. };
  223. mod.getSelectedCursors = function() {
  224. cursorIds = [];
  225. /* We need to keep record of which show the cursor belongs to
  226. * in the case where more than one show is displayed in the show builder
  227. * because header and footer rows have the same id
  228. */
  229. showInstanceIds = [];
  230. /* Keeps track if the row is a footer. We need to do this because
  231. * header and footer rows have the save cursorIds and showInstanceId
  232. * so both will be selected in the draw callback
  233. */
  234. headerFooter = [];
  235. cursors = $(".cursor-selected-row");
  236. for (i = 0; i < cursors.length; i++) {
  237. cursorIds.push(($(cursors.get(i)).attr("id")));
  238. showInstanceIds.push(($(cursors.get(i)).attr("si_id")));
  239. if ($(cursors.get(i)).hasClass("sb-footer")) {
  240. headerFooter.push("f");
  241. } else {
  242. headerFooter.push("n");
  243. }
  244. }
  245. };
  246. mod.fnAdd = function(aMediaIds, aSchedIds) {
  247. mod.disableUI();
  248. $.post(baseUrl+"showbuilder/schedule-add",
  249. {"format": "json", "mediaIds": aMediaIds, "schedIds": aSchedIds},
  250. mod.fnItemCallback
  251. );
  252. };
  253. mod.fnMove = function(aSelect, aAfter) {
  254. mod.disableUI();
  255. $.post(baseUrl+"showbuilder/schedule-move",
  256. {"format": "json", "selectedItem": aSelect, "afterItem": aAfter},
  257. mod.fnItemCallback
  258. );
  259. };
  260. mod.fnRemove = function(aItems) {
  261. mod.disableUI();
  262. if (confirm($.i18n._("Delete selected item(s)?"))) {
  263. $.post( baseUrl+"showbuilder/schedule-remove",
  264. {"items": aItems, "format": "json"},
  265. mod.fnItemCallback
  266. );
  267. }else{
  268. mod.enableUI();
  269. }
  270. };
  271. mod.fnRemoveSelectedItems = function() {
  272. var aData = mod.getSelectedData(),
  273. i,
  274. length,
  275. temp,
  276. aItems = [];
  277. for (i=0, length = aData.length; i < length; i++) {
  278. temp = aData[i];
  279. aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp});
  280. }
  281. mod.fnRemove(aItems);
  282. };
  283. mod.fnServerData = function fnBuilderServerData( sSource, aoData, fnCallback ) {
  284. aoData.push( { name: "timestamp", value: mod.getTimestamp()} );
  285. aoData.push( { name: "instances", value: mod.getShowInstances()} );
  286. aoData.push( { name: "format", value: "json"} );
  287. if (mod.fnServerData.hasOwnProperty("start")) {
  288. aoData.push( { name: "start", value: mod.fnServerData.start} );
  289. }
  290. if (mod.fnServerData.hasOwnProperty("end")) {
  291. aoData.push( { name: "end", value: mod.fnServerData.end} );
  292. }
  293. if (mod.fnServerData.hasOwnProperty("ops")) {
  294. aoData.push( { name: "myShows", value: mod.fnServerData.ops.myShows} );
  295. aoData.push( { name: "showFilter", value: mod.fnServerData.ops.showFilter} );
  296. aoData.push( { name: "showInstanceFilter", value: mod.fnServerData.ops.showInstanceFilter} );
  297. }
  298. $.ajax({
  299. "dataType": "json",
  300. "type": "POST",
  301. "url": sSource,
  302. "data": aoData,
  303. "success": function(json) {
  304. mod.updateCalendarStatusIcon(json)
  305. mod.setTimestamp(json.timestamp);
  306. mod.setShowInstances(json.instances);
  307. mod.getSelectedCursors();
  308. fnCallback(json);
  309. }
  310. });
  311. };
  312. mod.jumpToCurrentTrack = function() {
  313. var $scroll = $sbContent.find(".dataTables_scrolling");
  314. var scrolled = $scroll.scrollTop();
  315. var scrollingTop = $scroll.offset().top;
  316. var oTable = $('#show_builder_table').dataTable();
  317. var current = $sbTable.find("."+NOW_PLAYING_CLASS);
  318. var currentTop = current.offset().top;
  319. $scroll.scrollTop(currentTop - scrollingTop + scrolled);
  320. }
  321. mod.builderDataTable = function() {
  322. $sbContent = $('#show_builder');
  323. $lib = $("#library_content"),
  324. $sbTable = $sbContent.find('table');
  325. var isInitialized = false;
  326. oSchedTable = $sbTable.dataTable( {
  327. "aoColumns": [
  328. /* checkbox */ {"mDataProp": "allowed", "sTitle": "", "sWidth": "15px", "sClass": "sb-checkbox"},
  329. /* Type */ {"mDataProp": "image", "sTitle": "", "sClass": "library_image sb-image", "sWidth": "16px"},
  330. /* starts */ {"mDataProp": "starts", "sTitle": $.i18n._("Start"), "sClass": "sb-starts", "sWidth": "60px"},
  331. /* ends */ {"mDataProp": "ends", "sTitle": $.i18n._("End"), "sClass": "sb-ends", "sWidth": "60px"},
  332. /* runtime */ {"mDataProp": "runtime", "sTitle": $.i18n._("Duration"), "sClass": "library_length sb-length", "sWidth": "65px"},
  333. /* title */ {"mDataProp": "title", "sTitle": $.i18n._("Title"), "sClass": "sb-title"},
  334. /* creator */ {"mDataProp": "creator", "sTitle": $.i18n._("Creator"), "sClass": "sb-creator"},
  335. /* album */ {"mDataProp": "album", "sTitle": $.i18n._("Album"), "sClass": "sb-album"},
  336. /* cue in */ {"mDataProp": "cuein", "sTitle": $.i18n._("Cue In"), "bVisible": false, "sClass": "sb-cue-in"},
  337. /* cue out */ {"mDataProp": "cueout", "sTitle": $.i18n._("Cue Out"), "bVisible": false, "sClass": "sb-cue-out"},
  338. /* fade in */ {"mDataProp": "fadein", "sTitle": $.i18n._("Fade In"), "bVisible": false, "sClass": "sb-fade-in"},
  339. /* fade out */ {"mDataProp": "fadeout", "sTitle": $.i18n._("Fade Out"), "bVisible": false, "sClass": "sb-fade-out"},
  340. /* Mime */ {"mDataProp" : "mime", "sTitle" : $.i18n._("Mime"), "bVisible": false, "sClass": "sb-mime"}
  341. ],
  342. "bJQueryUI": true,
  343. "bSort": false,
  344. "bFilter": false,
  345. "bProcessing": true,
  346. "bServerSide": true,
  347. "bInfo": false,
  348. "bAutoWidth": false,
  349. "bStateSave": true,
  350. "fnStateSaveParams": function (oSettings, oData) {
  351. //remove oData components we don't want to save.
  352. delete oData.oSearch;
  353. delete oData.aoSearchCols;
  354. },
  355. "fnStateSave": function fnStateSave(oSettings, oData) {
  356. localStorage.setItem('datatables-timeline', JSON.stringify(oData));
  357. $.ajax({
  358. url: baseUrl+"usersettings/set-timeline-datatable",
  359. type: "POST",
  360. data: {settings : oData, format: "json"},
  361. dataType: "json"
  362. });
  363. },
  364. "fnStateLoad": function fnBuilderStateLoad(oSettings) {
  365. var settings = localStorage.getItem('datatables-timeline');
  366. if (settings !== "") {
  367. return JSON.parse(settings);
  368. }
  369. },
  370. "fnStateLoadParams": function (oSettings, oData) {
  371. var i,
  372. length,
  373. a = oData.abVisCols;
  374. //putting serialized data back into the correct js type to make
  375. //sure everything works properly.
  376. for (i = 0, length = a.length; i < length; i++) {
  377. if (typeof(a[i]) === "string") {
  378. a[i] = (a[i] === "true") ? true : false;
  379. }
  380. }
  381. a = oData.ColReorder;
  382. for (i = 0, length = a.length; i < length; i++) {
  383. if (typeof(a[i]) === "string") {
  384. a[i] = parseInt(a[i], 10);
  385. }
  386. }
  387. oData.iCreate = parseInt(oData.iCreate, 10);
  388. },
  389. "fnServerData": mod.fnServerData,
  390. "fnRowCallback": function fnRowCallback( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
  391. var i, length,
  392. sSeparatorHTML,
  393. fnPrepareSeparatorRow,
  394. $node,
  395. cl="",
  396. //background-color to imitate calendar color.
  397. r,g,b,a,
  398. $nRow = $(nRow),
  399. $image,
  400. $div,
  401. headerIcon;
  402. fnPrepareSeparatorRow = function fnPrepareSeparatorRow(sRowContent, sClass, iNodeIndex) {
  403. $node = $(nRow.children[iNodeIndex]);
  404. $node.html(sRowContent);
  405. $node.attr('colspan',100);
  406. for (i = iNodeIndex + 1, length = nRow.children.length; i < length; i = i+1) {
  407. $node = $(nRow.children[i]);
  408. $node.html("");
  409. $node.attr("style", "display : none");
  410. }
  411. $nRow.addClass(sClass);
  412. };
  413. if (aData.header === true) {
  414. //remove the column classes from all tds.
  415. $nRow.find('td').removeClass();
  416. $node = $(nRow.children[0]);
  417. $node.html("");
  418. cl = 'sb-header';
  419. if (aData.record === true) {
  420. headerIcon = (aData.soundcloud_id > 0) ? "soundcloud" : "recording";
  421. $div = $("<div/>", {
  422. "class": "small-icon " + headerIcon
  423. });
  424. $node.append($div);
  425. }
  426. else if (aData.rebroadcast === true) {
  427. $div = $("<div/>", {
  428. "class": "small-icon rebroadcast"
  429. });
  430. $node.append($div);
  431. }
  432. sSeparatorHTML = '<span class="show-title">'+aData.title+'</span>';
  433. if (aData.rebroadcast === true) {
  434. sSeparatorHTML += '<span>'+aData.rebroadcast_title+'</span>';
  435. }
  436. sSeparatorHTML += '<span class="push-right">';
  437. if (aData.startDate === aData.endDate) {
  438. sSeparatorHTML += '<span class="show-date">'+aData.startDate+'</span><span class="show-time">'+aData.startTime+'</span>';
  439. sSeparatorHTML +='-<span class="show-time">'+aData.endTime+'</span>';
  440. }
  441. else {
  442. sSeparatorHTML += '<span class="show-date">'+aData.startDate+'</span><span class="show-time">'+aData.startTime+'</span>';
  443. sSeparatorHTML +='-<span class="show-date">'+aData.endDate+'</span><span class="show-time">'+aData.endTime+'</span>';
  444. }
  445. sSeparatorHTML += '</span>';
  446. fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
  447. }
  448. else if (aData.footer === true) {
  449. //remove the column classes from all tds.
  450. $nRow.find('td').removeClass();
  451. $node = $(nRow.children[0]);
  452. cl = 'sb-footer';
  453. //check the show's content status.
  454. if (aData.runtime >= 0) {
  455. $node.html('<span class="ui-icon ui-icon-check"></span>');
  456. cl = cl + ' ui-state-highlight';
  457. }
  458. else {
  459. $node.html('<span class="ui-icon ui-icon-notice"></span>');
  460. cl = cl + ' ui-state-error';
  461. }
  462. sSeparatorHTML = '<span>'+aData.fRuntime+'</span>';
  463. fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
  464. }
  465. else if (aData.empty === true) {
  466. //remove the column classes from all tds.
  467. $nRow.find('td').removeClass();
  468. $node = $(nRow.children[0]);
  469. $node.html('');
  470. sSeparatorHTML = '<span>'+$.i18n._("Show Empty")+'</span>';
  471. cl = cl + " sb-empty odd";
  472. fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
  473. }
  474. else if (aData.record === true) {
  475. //remove the column classes from all tds.
  476. $nRow.find('td').removeClass();
  477. $node = $(nRow.children[0]);
  478. $node.html('');
  479. sSeparatorHTML = '<span>'+$.i18n._("Recording From Line In")+'</span>';
  480. cl = cl + " sb-record odd";
  481. fnPrepareSeparatorRow(sSeparatorHTML, cl, 1);
  482. }
  483. else {
  484. //add the play function if the file exists on disk.
  485. $image = $nRow.find('td.sb-image');
  486. //check if the file exists.
  487. if (aData.image === true) {
  488. $nRow.addClass("lib-audio");
  489. if (!isAudioSupported(aData.mime)) {
  490. $image.html('<span class="ui-icon ui-icon-locked"></span>');
  491. } else {
  492. $image.html('<img title="'+$.i18n._("Track preview")+'" src="'+baseUrl+'css/images/icon_audioclip.png"></img>')
  493. .click(function() {
  494. open_show_preview(aData.instance, aData.pos);
  495. return false;
  496. });
  497. }
  498. }
  499. else {
  500. $image.html('<span class="ui-icon ui-icon-alert"></span>');
  501. $image.find(".ui-icon-alert").qtip({
  502. content: {
  503. text: $.i18n._("Airtime is unsure about the status of this file. This can happen when the file is on a remote drive that is unaccessible or the file is in a directory that isn't \"watched\" anymore.")
  504. },
  505. style: {
  506. classes: "ui-tooltip-dark"
  507. },
  508. show: 'mouseover',
  509. hide: 'mouseout'
  510. });
  511. }
  512. $node = $(nRow.children[0]);
  513. if (aData.allowed === true && aData.scheduled >= 1 && aData.linked_allowed) {
  514. $node.html('<input type="checkbox" name="'+aData.id+'"></input>');
  515. }
  516. else {
  517. $node.html('');
  518. }
  519. }
  520. //add the show colour to the leftmost td
  521. if (aData.footer !== true) {
  522. if ($nRow.hasClass('sb-header')) {
  523. a = 1;
  524. }
  525. else if ($nRow.hasClass('odd')) {
  526. a = 0.3;
  527. }
  528. else if ($nRow.hasClass('even')) {
  529. a = 0.4;
  530. }
  531. //convert from hex to rgb.
  532. r = parseInt((aData.backgroundColor).substring(0,2), 16);
  533. g = parseInt((aData.backgroundColor).substring(2,4), 16);
  534. b = parseInt((aData.backgroundColor).substring(4,6), 16);
  535. $nRow.find('td:first').css('background', 'rgba('+r+', '+g+', '+b+', '+a+')');
  536. }
  537. //save some info for reordering purposes.
  538. $nRow.data({"aData": aData});
  539. if (aData.scheduled === 1) {
  540. $nRow.addClass(NOW_PLAYING_CLASS);
  541. }
  542. else if (aData.scheduled === 0) {
  543. $nRow.addClass("sb-past");
  544. }
  545. else {
  546. $nRow.addClass("sb-future");
  547. }
  548. if (aData.allowed !== true || aData.linked_allowed === false) {
  549. $nRow.addClass("sb-not-allowed");
  550. }
  551. else {
  552. $nRow.addClass("sb-allowed");
  553. $nRow.attr("id", aData.id);
  554. $nRow.attr("si_id", aData.instance);
  555. }
  556. //status used to colour tracks.
  557. if (aData.status === 2) {
  558. $nRow.addClass("sb-boundry");
  559. }
  560. else if (aData.status === 0) {
  561. $nRow.addClass("sb-over");
  562. }
  563. if (aData.currentShow === true) {
  564. $nRow.addClass("sb-current-show");
  565. }
  566. //call the context menu so we can prevent the event from propagating.
  567. $nRow.find('td:gt(1)').click(function(e){
  568. $(this).contextMenu({x: e.pageX, y: e.pageY});
  569. return false;
  570. });
  571. },
  572. //remove any selected nodes before the draw.
  573. "fnPreDrawCallback": function( oSettings ) {
  574. //make sure any dragging helpers are removed or else they'll be stranded on the screen.
  575. $("#draggingContainer").remove();
  576. },
  577. "fnDrawCallback": function fnBuilderDrawCallback(oSettings, json) {
  578. var isInitialized = false;
  579. if (!isInitialized) {
  580. //when coming to 'Now Playing' page we want the page
  581. //to jump to the current track
  582. if ($(this).find("."+NOW_PLAYING_CLASS).length > 0) {
  583. mod.jumpToCurrentTrack();
  584. }
  585. }
  586. isInitialized = true;
  587. var wrapperDiv,
  588. markerDiv,
  589. $td,
  590. aData,
  591. elements,
  592. i, length, temp,
  593. $cursorRows,
  594. $table = $(this),
  595. $parent = $table.parent(),
  596. $tr,
  597. //use this array to cache DOM heights then we can detach the table to manipulate it to increase speed.
  598. heights = [];
  599. clearTimeout(mod.timeout);
  600. //only create the cursor arrows if the library is on the page.
  601. if ($lib.length > 0 && $lib.filter(":visible").length > 0) {
  602. $cursorRows = $sbTable.find("tbody tr.sb-future.sb-allowed:not(.sb-header, .sb-empty)");
  603. //need to get heights of tds while elements are still in the DOM.
  604. for (i = 0, length = $cursorRows.length; i < length; i++) {
  605. $td = $($cursorRows.get(i)).find("td:first");
  606. heights.push($td.height());
  607. }
  608. //detach the table to increase speed.
  609. $table.detach();
  610. for (i = 0, length = $cursorRows.length; i < length; i++) {
  611. $td = $($cursorRows.get(i)).find("td:first");
  612. if ($td.hasClass("dataTables_empty")) {
  613. $parent.append($table);
  614. return false;
  615. }
  616. wrapperDiv = $("<div />", {
  617. "class": "innerWrapper",
  618. "css": {
  619. "height": heights[i]
  620. }
  621. });
  622. markerDiv = $("<div />", {
  623. "class": "marker"
  624. });
  625. $td.append(markerDiv).wrapInner(wrapperDiv);
  626. }
  627. //re-highlight selected cursors before draw took place
  628. for (i = 0; i < cursorIds.length; i++) {
  629. if (headerFooter[i] == "f") {
  630. $tr = $table.find("tbody tr.sb-footer[id="+cursorIds[i]+"][si_id="+showInstanceIds[i]+"]");
  631. } else {
  632. $tr = $table.find("tr[id="+cursorIds[i]+"][si_id="+showInstanceIds[i]+"]");
  633. }
  634. /* If the currently playing track's cursor is selected,
  635. * and that track is deleted, the cursor position becomes
  636. * unavailble. We have to check the position is available
  637. * before re-highlighting it.
  638. */
  639. if ($tr.find(".sb-checkbox").children().hasClass("innerWrapper")) {
  640. mod.selectCursor($tr);
  641. /* If the selected cursor is the footer row we need to
  642. * explicitly select it because that row does not have
  643. * innerWrapper class
  644. */
  645. } else if ($tr.hasClass("sb-footer")) {
  646. mod.selectCursor($tr);
  647. }
  648. }
  649. //if there is only 1 cursor on the page highlight it by default.
  650. if ($cursorRows.length === 1) {
  651. $td = $cursorRows.find("td:first");
  652. if (!$td.hasClass("dataTables_empty")) {
  653. $cursorRows.addClass("cursor-selected-row");
  654. }
  655. }
  656. $parent.append($table);
  657. }
  658. //order of importance of elements for setting the next timeout.
  659. elements = [
  660. $sbTable.find("tr."+NOW_PLAYING_CLASS),
  661. $sbTable.find("tbody").find("tr.sb-future.sb-footer, tr.sb-future.sb-header").filter(":first")
  662. ];
  663. //check which element we should set a timeout relative to.
  664. for (i = 0, length = elements.length; i < length; i++) {
  665. temp = elements[i];
  666. if (temp.length > 0) {
  667. aData = temp.data("aData");
  668. // max time interval
  669. // setTimeout allows only up to (2^31)-1 millisecs timeout value
  670. maxRefreshInterval = Math.pow(2, 31) - 1;
  671. refreshInterval = aData.refresh * 1000;
  672. if(refreshInterval > maxRefreshInterval){
  673. refreshInterval = maxRefreshInterval;
  674. }
  675. mod.timeout = setTimeout(function() {mod.refresh(aData.id)}, refreshInterval); //need refresh in milliseconds
  676. break;
  677. }
  678. }
  679. mod.checkToolBarIcons();
  680. },
  681. // R = ColReorder, C = ColVis
  682. "sDom": 'R<"dt-process-rel"r><"sb-padded"<"H"C>><"dataTables_scrolling sb-padded"t>',
  683. "oColVis": {
  684. "aiExclude": [ 0, 1 ],
  685. "buttonText": $.i18n._("Show / hide columns"),
  686. },
  687. "oColReorder": {
  688. "iFixedColumns": 2
  689. },
  690. "sAjaxDataProp": "schedule",
  691. "oLanguage": datatables_dict,
  692. "sAjaxSource": baseUrl+"showbuilder/builder-feed"
  693. });
  694. $sbTable.find("tbody").on("click", "input:checkbox", function(ev) {
  695. var $cb = $(this),
  696. $tr = $cb.parents("tr"),
  697. $prev;
  698. if ($cb.is(":checked")) {
  699. if (ev.shiftKey) {
  700. $prev = $sbTable.find("tbody").find("tr."+SB_SELECTED_CLASS).eq(-1);
  701. $prev.nextUntil($tr)
  702. .addClass(SB_SELECTED_CLASS)
  703. .find("input:checkbox")
  704. .attr("checked", true)
  705. .end();
  706. }
  707. $tr.addClass(SB_SELECTED_CLASS);
  708. }
  709. else {
  710. $tr.removeClass(SB_SELECTED_CLASS);
  711. }
  712. mod.checkToolBarIcons();
  713. });
  714. var sortableConf = (function(){
  715. var origTrs,
  716. aItemData = [],
  717. oPrevData,
  718. fnAdd,
  719. fnMove,
  720. fnReceive,
  721. fnUpdate,
  722. i,
  723. html,
  724. helperData,
  725. draggingContainer;
  726. fnAdd = function() {
  727. var aMediaIds = [],
  728. aSchedIds = [];
  729. for(i = 0; i < aItemData.length; i++) {
  730. aMediaIds.push({"id": aItemData[i].id, "type": aItemData[i].ftype});
  731. }
  732. aSchedIds.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
  733. mod.fnAdd(aMediaIds, aSchedIds);
  734. };
  735. fnMove = function() {
  736. var aSelect = [],
  737. aAfter = [];
  738. for(i = 0; i < helperData.length; i++) {
  739. aSelect.push({"id": helperData[i].id, "instance": helperData[i].instance, "timestamp": helperData[i].timestamp});
  740. }
  741. aAfter.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp});
  742. mod.fnMove(aSelect, aAfter);
  743. };
  744. fnReceive = function(event, ui) {
  745. var aItems = [];
  746. AIRTIME.library.addToChosen(ui.item);
  747. aItems = AIRTIME.library.getSelectedData();
  748. origTrs = aItems;
  749. html = ui.helper.html();
  750. AIRTIME.library.removeFromChosen(ui.item);
  751. };
  752. fnUpdate = function(event, ui) {
  753. var prev = ui.item.prev();
  754. //can't add items outside of shows.
  755. if (prev.find("td:first").hasClass("dataTables_empty")
  756. || prev.length === 0) {
  757. alert($.i18n._("Cannot schedule outside a show."));
  758. ui.item.remove();
  759. return;
  760. }
  761. //if item is added after a footer, add the item after the last item in the show.
  762. if (prev.hasClass("sb-footer")) {
  763. prev = prev.prev();
  764. }
  765. aItemData = [];
  766. oPrevData = prev.data("aData");
  767. //item was dragged in
  768. if (origTrs !== undefined) {
  769. $sbTable.find("tr.ui-draggable")
  770. .empty()
  771. .after(html);
  772. aItemData = origTrs;
  773. origTrs = undefined;
  774. fnAdd();
  775. }
  776. //item was reordered.
  777. else {
  778. ui.item
  779. .empty()
  780. .after(draggingContainer.html());
  781. aItemData.push(ui.item.data("aData"));
  782. fnMove();
  783. }
  784. };
  785. return {
  786. placeholder: "sb-placeholder ui-state-highlight",
  787. forcePlaceholderSize: true,
  788. distance: 10,
  789. helper: function(event, item) {
  790. var selected = mod.getSelectedData(NOW_PLAYING_CLASS),
  791. thead = $("#show_builder_table thead"),
  792. colspan = thead.find("th").length,
  793. trfirst = thead.find("tr:first"),
  794. width = trfirst.width(),
  795. height = trfirst.height(),
  796. message;
  797. //if nothing is checked select the dragged item.
  798. if (selected.length === 0) {
  799. selected = [item.data("aData")];
  800. }
  801. if (selected.length === 1) {
  802. message = $.i18n._("Moving 1 Item");
  803. }
  804. else {
  805. message = sprintf($.i18n._("Moving %s Items"), selected.length);
  806. }
  807. draggingContainer = $('<tr/>')
  808. .addClass('sb-helper')
  809. .append('<td/>')
  810. .find("td")
  811. .attr("colspan", colspan)
  812. .width(width)
  813. .height(height)
  814. .addClass("ui-state-highlight")
  815. .append(message)
  816. .end();
  817. helperData = selected;
  818. return draggingContainer;
  819. },
  820. items: 'tr:not(:first, :last, .sb-header, .sb-not-allowed, .sb-past, .sb-now-playing, .sb-empty)',
  821. cancel: '.sb-footer',
  822. receive: fnReceive,
  823. update: fnUpdate,
  824. start: function(event, ui) {
  825. /*
  826. var elements = $sbTable.find('tr input:checked').parents('tr')
  827. .not(ui.item)
  828. .not("."+NOW_PLAYING_CLASS);
  829. //remove all other items from the screen,
  830. //don't remove ui.item or else we can not get position information when the user drops later.
  831. elements.remove();
  832. */
  833. var elements = $sbTable.find('tr input:checked').parents('tr').not("."+NOW_PLAYING_CLASS);
  834. elements.hide();
  835. }
  836. };
  837. }());
  838. $sbTable.sortable(sortableConf);
  839. //start setup of the builder toolbar.
  840. $toolbar = $(".sb-content .fg-toolbar");
  841. $menu = $("<div class='btn-toolbar'/>");
  842. $menu.append("<div class='btn-group'>" +
  843. "<button class='btn btn-small dropdown-toggle' id='timeline-select' data-toggle='dropdown'>" +
  844. $.i18n._("Select")+" <span class='caret'></span>" +
  845. "</button>" +
  846. "<ul class='dropdown-menu'>" +
  847. "<li id='timeline-sa'><a href='#'>"+$.i18n._("Select all")+"</a></li>" +
  848. "<li id='timeline-sn'><a href='#'>"+$.i18n._("Select none")+"</a></li>" +
  849. "</ul>" +
  850. "</div>")
  851. .append("<div class='btn-group'>" +
  852. "<button title='"+$.i18n._("Remove overbooked tracks")+"' class='ui-state-disabled btn btn-small' disabled='disabled'>" +
  853. "<i class='icon-white icon-cut'></i></button></div>")
  854. .append("<div class='btn-group'>" +
  855. "<button title='"+$.i18n._("Remove selected scheduled items")+"' class='ui-state-disabled btn btn-small' disabled='disabled'>" +
  856. "<i class='icon-white icon-trash'></i></button></div>");
  857. //if 'Add/Remove content' was chosen from the context menu
  858. //in the Calendar do not append these buttons
  859. if ($(".ui-dialog-content").length === 0) {
  860. $menu.append("<div class='btn-group'>" +
  861. "<button title='"+$.i18n._("Jump to the current playing track")+"' class='ui-state-disabled btn btn-small' disabled='disabled'>" +
  862. "<i class='icon-white icon-step-forward'></i></button></div>")
  863. .append("<div class='btn-group'>" +
  864. "<button title='"+$.i18n._("Cancel current show")+"' class='ui-state-disabled btn btn-small btn-danger' disabled='disabled'>" +
  865. "<i class='icon-white icon-ban-circle'></i></button></div>");
  866. }
  867. if (localStorage.getItem('user-type') != 'G') {
  868. $toolbar.append($menu);
  869. }
  870. $menu = undefined;
  871. $('#timeline-sa').click(function(){mod.selectAll();});
  872. $('#timeline-sn').click(function(){mod.selectNone();});
  873. //cancel current show
  874. $toolbar.find('.icon-ban-circle').parent()
  875. .click(function() {
  876. var $tr,
  877. data,
  878. msg = $.i18n._('Cancel Current Show?');
  879. if (AIRTIME.button.isDisabled('icon-ban-circle', true) === true) {
  880. return;
  881. }
  882. $tr = $sbTable.find('tr.sb-future:first');
  883. if ($tr.hasClass('sb-current-show')) {
  884. data = $tr.data("aData");
  885. if (data.record === true) {
  886. msg = $.i18n._('Stop recording current show?');
  887. }
  888. if (confirm(msg)) {
  889. var url = baseUrl+"Schedule/cancel-current-show";
  890. $.ajax({
  891. url: url,
  892. data: {format: "json", id: data.instance},
  893. success: function(data){
  894. $("#library_content").find("#library_display").dataTable().fnStandingRedraw();
  895. var oTable = $sbTable.dataTable();
  896. oTable.fnDraw();
  897. }
  898. });
  899. }
  900. }
  901. });
  902. //jump to current
  903. $toolbar.find('.icon-step-forward').parent()
  904. .click(function() {
  905. if (AIRTIME.button.isDisabled('icon-step-forward', true) === true) {
  906. return;
  907. }
  908. /*
  909. var $scroll = $sbContent.find(".dataTables_scrolling"),
  910. scrolled = $scroll.scrollTop(),
  911. scrollingTop = $scroll.offset().top,
  912. current = $sbTable.find("."+NOW_PLAYING_CLASS),
  913. currentTop = current.offset().top;
  914. $scroll.scrollTop(currentTop - scrollingTop + scrolled);
  915. */
  916. mod.jumpToCurrentTrack();
  917. });
  918. //delete overbooked tracks.
  919. $toolbar.find('.icon-cut', true).parent()
  920. .click(function() {
  921. if (AIRTIME.button.isDisabled('icon-cut', true) === true) {
  922. return;
  923. }
  924. var temp,
  925. aItems = [],
  926. trs = $sbTable.find(".sb-over.sb-future.sb-allowed");
  927. trs.each(function(){
  928. temp = $(this).data("aData");
  929. aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp});
  930. });
  931. mod.fnRemove(aItems);
  932. });
  933. //delete selected tracks
  934. $toolbar.find('.icon-trash').parent()
  935. .click(function() {
  936. if (AIRTIME.button.isDisabled('icon-trash', true) === true) {
  937. return;
  938. }
  939. mod.fnRemoveSelectedItems();
  940. });
  941. //add events to cursors.
  942. $sbTable.find("tbody").on("click", "div.marker", function(event) {
  943. var $tr = $(this).parents("tr"),
  944. $trs;
  945. if ($tr.hasClass(CURSOR_SELECTED_CLASS)) {
  946. mod.removeCursor($tr);
  947. }
  948. else {
  949. mod.selectCursor($tr);
  950. }
  951. if (event.ctrlKey === false) {
  952. $trs = $sbTable.find('.'+CURSOR_SELECTED_CLASS).not($tr);
  953. mod.removeCursor($trs);
  954. }
  955. return false;
  956. });
  957. /*
  958. * Select button dropdown state in the toolbar.
  959. * The button has to be disabled to prevent the dropdown
  960. * from opening
  961. */
  962. $sbContent.on("mouseenter", ".btn-group #timeline-select", function(ev) {
  963. $el = $(this);
  964. if ($el.hasClass("ui-state-disabled")) {
  965. $el.attr("disabled", "disabled");
  966. }
  967. else {
  968. $el.removeAttr("disabled");
  969. }
  970. });
  971. //begin context menu initialization.
  972. $.contextMenu({
  973. selector: '.sb-content table tbody tr:not(.sb-empty, .sb-footer, .sb-header, .sb-record) td:not(.sb-checkbox, .sb-image)',
  974. trigger: "left",
  975. ignoreRightClick: true,
  976. build: function($el, e) {
  977. var items,
  978. $tr = $el.parent(),
  979. data = $tr.data("aData"),
  980. cursorClass = "cursor-selected-row",
  981. callback;
  982. function processMenuItems(oItems) {
  983. //define a preview callback.
  984. if (oItems.preview !== undefined) {
  985. callback = function() {
  986. open_show_preview(data.instance, data.pos);
  987. };
  988. oItems.preview.callback = callback;
  989. }
  990. //define a select cursor callback.
  991. if (oItems.selCurs !== undefined) {
  992. callback = function() {
  993. var $tr = $(this).parents('tr').next();
  994. mod.selectCursor($tr);
  995. };
  996. oItems.selCurs.callback = callback;
  997. }
  998. //define a remove cursor callback.
  999. if (oItems.delCurs !== undefined) {
  1000. callback = function() {
  1001. var $tr = $(this).parents('tr').next();
  1002. mod.removeCursor($tr);
  1003. };
  1004. oItems.delCurs.callback = callback;
  1005. }
  1006. //define a delete callback.
  1007. if (oItems.del !== undefined) {
  1008. callback = function() {
  1009. AIRTIME.showbuilder.fnRemove([{
  1010. id: data.id,
  1011. timestamp: data.timestamp,
  1012. instance: data.instance
  1013. }]);
  1014. };
  1015. oItems.del.callback = callback;
  1016. }
  1017. //only show the cursor selecting options if the library is visible on the page.
  1018. if ($tr.next().find('.marker').length === 0) {
  1019. delete oItems.selCurs;
  1020. delete oItems.delCurs;
  1021. }
  1022. //check to include either select or remove cursor.
  1023. else {
  1024. if ($tr.next().hasClass(cursorClass)) {
  1025. delete oItems.selCurs;
  1026. }
  1027. else {
  1028. delete oItems.delCurs;
  1029. }
  1030. }
  1031. items = oItems;
  1032. }
  1033. request = $.ajax({
  1034. url: baseUrl+"showbuilder/context-menu",
  1035. type: "GET",
  1036. data: {id : data.id, format: "json"},
  1037. dataType: "json",
  1038. async: false,
  1039. success: function(json){
  1040. processMenuItems(json.items);
  1041. }
  1042. });
  1043. return {
  1044. items: items
  1045. };
  1046. }
  1047. });
  1048. };
  1049. return AIRTIME;
  1050. }(AIRTIME || {}));