diff --git a/airtime_mvc/application/Bootstrap.php b/airtime_mvc/application/Bootstrap.php index e64c74136..863ae1ecc 100644 --- a/airtime_mvc/application/Bootstrap.php +++ b/airtime_mvc/application/Bootstrap.php @@ -33,7 +33,7 @@ require_once "TaskManager.php"; require_once "UsabilityHints.php"; require_once "MediaType.php"; require_once __DIR__.'/models/formatters/LengthFormatter.php'; -require_once __DIR__.'/services/CeleryService.php'; +require_once __DIR__.'/services/CeleryManager.php'; require_once __DIR__.'/services/SoundcloudService.php'; require_once __DIR__.'/forms/helpers/ValidationTypes.php'; require_once __DIR__.'/forms/helpers/CustomDecorators.php'; diff --git a/airtime_mvc/application/common/TaskManager.php b/airtime_mvc/application/common/TaskManager.php index c1687defa..0275d08f3 100644 --- a/airtime_mvc/application/common/TaskManager.php +++ b/airtime_mvc/application/common/TaskManager.php @@ -237,14 +237,14 @@ class CeleryTask implements AirtimeTask { * @return bool true if there are pending tasks in ThirdPartyTrackReferences */ public function shouldBeRun() { - return !CeleryService::isBrokerTaskQueueEmpty(); + return !CeleryManager::isBrokerTaskQueueEmpty(); } /** * Poll the task queue for any completed Celery tasks */ public function run() { - CeleryService::pollBrokerTaskQueue(); + CeleryManager::pollBrokerTaskQueue(); } } \ No newline at end of file diff --git a/airtime_mvc/application/controllers/ShowbuilderController.php b/airtime_mvc/application/controllers/ShowbuilderController.php index df3d01294..619f515b8 100644 --- a/airtime_mvc/application/controllers/ShowbuilderController.php +++ b/airtime_mvc/application/controllers/ShowbuilderController.php @@ -56,6 +56,7 @@ class ShowbuilderController extends Zend_Controller_Action $this->view->headScript()->appendFile($baseUrl.'js/libs/dropzone.min.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/timepicker/jquery.ui.timepicker.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); + $this->view->headScript()->appendFile($baseUrl.'js/airtime/showbuilder/tabs.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/airtime/showbuilder/builder.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/airtime/showbuilder/main_builder.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); diff --git a/airtime_mvc/application/services/CeleryService.php b/airtime_mvc/application/services/CeleryManager.php similarity index 99% rename from airtime_mvc/application/services/CeleryService.php rename to airtime_mvc/application/services/CeleryManager.php index b05692410..07ab7fa42 100644 --- a/airtime_mvc/application/services/CeleryService.php +++ b/airtime_mvc/application/services/CeleryManager.php @@ -2,7 +2,7 @@ require_once "CeleryServiceFactory.php"; -class CeleryService { +class CeleryManager { /** * @var int milliseconds (for compatibility with celery) until we consider a message to have timed out diff --git a/airtime_mvc/application/services/ThirdPartyCeleryService.php b/airtime_mvc/application/services/ThirdPartyCeleryService.php index 2a7b12606..24e9a5800 100644 --- a/airtime_mvc/application/services/ThirdPartyCeleryService.php +++ b/airtime_mvc/application/services/ThirdPartyCeleryService.php @@ -24,6 +24,30 @@ abstract class ThirdPartyCeleryService extends ThirdPartyService { */ protected static $_CELERY_DELETE_TASK_NAME; + /** + * Execute a Celery task with the given name and data parameters + * + * FIXME: Currently, downloads will not create task reference rows because they + * don't have a valid file identifier - this means that we will never know if there + * is an issue with the download before the callback to /rest/media is called! + * + * @param string $taskName the name of the celery task to execute + * @param array $data the data array to send as task parameters + * @param int $fileId the unique identifier for the file involved in the task + */ + protected function _executeTask($taskName, $data, $fileId) { + try { + $brokerTaskId = CeleryManager::sendCeleryMessage($taskName, + static::$_CELERY_EXCHANGE_NAME, + $data); + if (!empty($fileId)) { + $this->_createTaskReference($fileId, $brokerTaskId, $taskName); + } + } catch (Exception $e) { + Logging::info("Invalid request: " . $e->getMessage()); + } + } + /** * Upload the file with the given identifier to a third-party service * @@ -36,20 +60,13 @@ abstract class ThirdPartyCeleryService extends ThirdPartyService { 'token' => $this->_accessToken, 'file_path' => $file->getFilePaths()[0] ); - try { - $brokerTaskId = CeleryService::sendCeleryMessage(static::$_CELERY_UPLOAD_TASK_NAME, - static::$_CELERY_EXCHANGE_NAME, - $data); - $this->_createTaskReference($fileId, $brokerTaskId, static::$_CELERY_UPLOAD_TASK_NAME); - } catch (Exception $e) { - Logging::info("Invalid request: " . $e->getMessage()); - } + $this->_executeTask(static::$_CELERY_UPLOAD_TASK_NAME, $data, $fileId); } /** * Given a track identifier, download a track from a third-party service. * - * @param int|null $trackId a track identifier + * @param int $trackId a track identifier */ public function download($trackId) { $namespace = new Zend_Session_Namespace('csrf_namespace'); @@ -59,13 +76,9 @@ abstract class ThirdPartyCeleryService extends ThirdPartyService { 'token' => $this->_accessToken, 'track_id' => $trackId ); - try { - CeleryService::sendCeleryMessage(static::$_CELERY_DOWNLOAD_TASK_NAME, - static::$_CELERY_EXCHANGE_NAME, - $data); - } catch (Exception $e) { - Logging::info("Invalid request: " . $e->getMessage()); - } + // FIXME + Logging::warn("FIXME: we can't create a task reference without a valid file ID"); + $this->_executeTask(static::$_CELERY_DOWNLOAD_TASK_NAME, $data, null); } /** @@ -85,14 +98,7 @@ abstract class ThirdPartyCeleryService extends ThirdPartyService { 'token' => $this->_accessToken, 'track_id' => $serviceId ); - try { - $brokerTaskId = CeleryService::sendCeleryMessage(static::$_CELERY_DELETE_TASK_NAME, - static::$_CELERY_EXCHANGE_NAME, - $data); - $this->_createTaskReference($fileId, $brokerTaskId, static::$_CELERY_DELETE_TASK_NAME); - } catch (Exception $e) { - Logging::info("Invalid request: " . $e->getMessage()); - } + $this->_executeTask(static::$_CELERY_DELETE_TASK_NAME, $data, $fileId); } /** @@ -108,19 +114,19 @@ abstract class ThirdPartyCeleryService extends ThirdPartyService { * @throws PropelException */ protected function _createTaskReference($fileId, $brokerTaskId, $taskName) { - $trackId = $this->createTrackReference($fileId); + $trackReferenceId = $this->createTrackReference($fileId); $task = new CeleryTasks(); $task->setDbTaskId($brokerTaskId); $task->setDbName($taskName); $utc = new DateTimeZone("UTC"); $task->setDbDispatchTime(new DateTime("now", $utc)); $task->setDbStatus(CELERY_PENDING_STATUS); - $task->setDbTrackReference($trackId); + $task->setDbTrackReference($trackReferenceId); $task->save(); } /** - * Update a CeleryTasks object for a completed upload + * Update a CeleryTasks object for a completed task * TODO: should we have a database layer class to handle Propel operations? * * @param $trackId int ThirdPartyTrackReferences identifier diff --git a/airtime_mvc/application/services/ThirdPartyService.php b/airtime_mvc/application/services/ThirdPartyService.php index d3293f18c..523e0e5cc 100644 --- a/airtime_mvc/application/services/ThirdPartyService.php +++ b/airtime_mvc/application/services/ThirdPartyService.php @@ -54,7 +54,7 @@ abstract class ThirdPartyService { } /** - * Remove a ThirdPartyTrackReferences from the database. + * Remove a ThirdPartyTrackReferences row from the database. * This is necessary if the track was removed from the service * or the foreign id in our database is incorrect * diff --git a/airtime_mvc/public/css/dashboard.css b/airtime_mvc/public/css/dashboard.css index d6a668d48..bf0a3ec2e 100644 --- a/airtime_mvc/public/css/dashboard.css +++ b/airtime_mvc/public/css/dashboard.css @@ -481,6 +481,14 @@ li.ui-state-default { overflow-x: hidden; /* Show the y-direction scrollbar (magic!) */ } +.tab-name { + float: left; + max-width: 150px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + .inner_editor_wrapper { max-height: 60%; overflow-x: hidden; diff --git a/airtime_mvc/public/js/airtime/common/common.js b/airtime_mvc/public/js/airtime/common/common.js index 70d931ae5..4fe28eae7 100644 --- a/airtime_mvc/public/js/airtime/common/common.js +++ b/airtime_mvc/public/js/airtime/common/common.js @@ -289,3 +289,18 @@ function getUsabilityHint() { }); } +function setupTextScrolling(parent, selector) { + parent.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); + } + }); + parent.on("mouseleave", selector, function () { + $(this).stop().animate({ + textIndent: "0" + }, 500); + }); +} \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index cb03e112b..b79ebbdd0 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -76,35 +76,35 @@ var AIRTIME = (function(AIRTIME) { mod.placeholder = function(mediaType) { switch (mediaType) { // TODO: remove duplication in a nice way? - case MediaTypeEnum.FILE: + case mod.MediaTypeEnum.FILE: return { "media": "tracks", "icon": "icon-music", "subtext": "Click 'Upload' to add some now.", "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/add-media/" }; - case MediaTypeEnum.PLAYLIST: + case mod.MediaTypeEnum.PLAYLIST: return { "media": "playlists", "icon": "icon-list", "subtext": "Click 'New' to create one now.", "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" }; - case MediaTypeEnum.BLOCK: + case mod.MediaTypeEnum.BLOCK: return { "media": "smart blocks", "icon": "icon-time", "subtext": "Click 'New' to create one now.", "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" }; - case MediaTypeEnum.WEBSTREAM: + case mod.MediaTypeEnum.WEBSTREAM: return { "media": "webstreams", "icon": "icon-random", "subtext": "Click 'New' to create one now.", "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" }; - case MediaTypeEnum.PODCAST: + case mod.MediaTypeEnum.PODCAST: return { "media": "podcasts", "icon": "icon-headphones", @@ -448,7 +448,7 @@ var AIRTIME = (function(AIRTIME) { openTabObjectIds.each(function(i, el) { var v = parseInt($(el).val()); if ($.inArray(v, mediaIds) > -1) { - AIRTIME.playlist.closeTab($(el).closest(".pl-content").attr("data-tab-id")); + AIRTIME.tabs.closeTab($(el).closest(".pl-content").attr("data-tab-id")); } }); @@ -710,24 +710,6 @@ var AIRTIME = (function(AIRTIME) { } else if (!aData.is_playlist) { $(nRow).find("td.library_is_playlist").html(''); } - - // add the play function to the library_type td - $(nRow).find('td.library_type').click(function () { - if (aData.ftype === 'playlist' && aData.length !== '0.0') { - open_playlist_preview(aData.audioFile, 0); - } else if (aData.ftype === 'audioclip') { - if (isAudioSupported(aData.mime)) { - open_audio_preview(aData.ftype, aData.id); - } - } else if (aData.ftype == 'stream') { - if (isAudioSupported(aData.mime)) { - open_audio_preview(aData.ftype, aData.id); - } - } else if (aData.ftype == 'block' && aData.bl_type == 'static') { - open_block_preview(aData.audioFile, 0); - } - return false; - }); }, // remove any selected nodes before the draw. "fnPreDrawCallback": function (oSettings) { @@ -985,6 +967,26 @@ var AIRTIME = (function(AIRTIME) { } }); + // add the play function to the library_type td + $libTable.on("click", "td.library_type", function () { + var aData = $(this).parent().data().aData; + + if (aData.ftype === 'playlist' && aData.length !== '0.0') { + open_playlist_preview(aData.audioFile, 0); + } else if (aData.ftype === 'audioclip') { + if (isAudioSupported(aData.mime)) { + open_audio_preview(aData.ftype, aData.id); + } + } else if (aData.ftype == 'stream') { + if (isAudioSupported(aData.mime)) { + open_audio_preview(aData.ftype, aData.id); + } + } else if (aData.ftype == 'block' && aData.bl_type == 'static') { + open_block_preview(aData.audioFile, 0); + } + return false; + }); + $libTable.find("tbody").on("mousedown", "tr[class*='lib'] > td:not(.library_checkbox, .dataTables_empty)", function(ev) { var $tr = $(this).parent(), // Get the ID of the selected row diff --git a/airtime_mvc/public/js/airtime/library/plupload.js b/airtime_mvc/public/js/airtime/library/plupload.js index 21a92de9f..01b9fefe2 100644 --- a/airtime_mvc/public/js/airtime/library/plupload.js +++ b/airtime_mvc/public/js/airtime/library/plupload.js @@ -157,27 +157,12 @@ $(document).ready(function () { getUsabilityHint(); } }); - }, - "fnCreatedRow": function(nRow) { - $(nRow).find("td").hover( - function () { - var sw = $(this)[0].scrollWidth, iw = $(this).innerWidth(); - if (sw > iw) { - $(this).stop().animate({ - textIndent: "-" + (sw - iw) + "px" - }, sw * 8); - } - }, - function () { - $(this).stop().animate({ - textIndent: "0" - }, 500); - } - ); } }); }; + setupTextScrolling($("#recent_uploads"), "td"); + self.isRecentUploadsRefreshTimerActive = false; self.startRefreshingRecentUploads = function () { diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 394a1bb1a..5ad412bcd 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -12,9 +12,7 @@ var AIRTIME = (function(AIRTIME){ viewport, $lib, $pl, - widgetHeight, - $tabCount = 0, - $openTabs = {}; + widgetHeight; function isTimeValid(time) { //var regExpr = new RegExp("^\\d{2}[:]\\d{2}[:]\\d{2}([.]\\d{1,6})?$"); @@ -356,17 +354,6 @@ var AIRTIME = (function(AIRTIME){ } } - function updateActiveTabName(newTabName) { - /* - var nameElement = $(this); - //remove any newlines if user somehow snuck them in (easy to do if dragging/dropping text) - nameElement.text(nameElement.text().replace("\n", "")); - - var name = $pl.find(".playlist_name_display").val(); - */ - $(".nav.nav-tabs .active a > span.tab-name").text(newTabName); - } - function redrawLib() { var dt = $lib.find("#library_display").dataTable(); @@ -394,7 +381,7 @@ var AIRTIME = (function(AIRTIME){ setCueEvents(); setFadeEvents(); mod.setModified(json.modified); - updateActiveTabName(json.name); + AIRTIME.tabs.setActiveTabName(json.name); AIRTIME.playlist.validatePlaylistElements(); redrawLib(); @@ -425,10 +412,10 @@ var AIRTIME = (function(AIRTIME){ $('.zend_form + .spl-no-margin > div:has(*:visible):last').css('margin-left', 0); } - function getId(pl) { + mod.getId = function(pl) { pl = (pl === undefined) ? $pl : pl; return parseInt(pl.find(".obj_id").val(), 10); - } + }; mod.getModified = function(pl) { pl = (pl === undefined) ? $pl : pl; @@ -443,115 +430,6 @@ var AIRTIME = (function(AIRTIME){ $pl.find(".title_obj_name").text(title); } - function setTabName(name) { - var id = $pl.data("tab-id"); - $("#show_builder").find("[data-tab-id='" + id + "']").find(".tab-name").text(name); - } - - /* - * Should all be moved to builder.js eventually - */ - function buildNewTab(json) { - AIRTIME.library.selectNone(); - - var tabId = $openTabs[json.type + json.id]; - if (tabId !== undefined) { - AIRTIME.showbuilder.switchTab($("#pl-tab-content-" + tabId), $("#pl-tab-" + tabId)); - return undefined; - } - $tabCount++; - - var wrapper = "
", - t = $("#show_builder").append(wrapper).find("#pl-tab-content-" + $tabCount), - pane = $(".editor_pane_wrapper:last"), - name = json.type == "md" ? // file - pane.append(json.html).find("#track_title").val() + $.i18n._(" - Metadata Editor") - : pane.append(json.html).find(".playlist_name_display").val(), - tab = - "", - tabs = $(".nav.nav-tabs"); - - if (json.id) { - $openTabs[json.type + json.id] = $tabCount; - } - - $(".nav.nav-tabs li").removeClass("active"); - tabs.append(tab); - tabs.find("#pl-tab-" + $tabCount + " span.tab-name").text(name); - - var newTab = $("#pl-tab-" + $tabCount); - AIRTIME.showbuilder.switchTab(t, newTab); - - return {wrapper: pane, tab: newTab, pane: t}; - } - - function openFileMdEditor(json) { - var newTab = buildNewTab(json); - if (newTab === undefined) { - return; - } - - initFileMdEvents(newTab); - initialEvents(); - } - - function initFileMdEvents(newTab) { - newTab.tab.on("click", function() { - if (!$(this).hasClass('active')) { - AIRTIME.showbuilder.switchTab(newTab.pane, newTab.tab); - } - }); - - newTab.wrapper.find(".md-cancel").on("click", function() { - closeTab(); - }); - - newTab.wrapper.find(".md-save").on("click", function() { - var file_id = newTab.wrapper.find('#file_id').val(), - data = newTab.wrapper.find("#edit-md-dialog form").serializeArray(); - $.post(baseUrl+'library/edit-file-md', {format: "json", id: file_id, data: data}, function() { - // don't redraw the library table if we are on calendar page - // we would be on calendar if viewing recorded file metadata - if ($("#schedule_calendar").length === 0) { - oTable.fnStandingRedraw(); - } - }); - - AIRTIME.playlist.closeTab(); - }); - - newTab.wrapper.find('#edit-md-dialog').on("keyup", function(event) { - if (event.keyCode === 13) { - newTab.wrapper.find('.md-save').click(); - } - }); - } - - function openPlaylist(json) { - var newTab = buildNewTab(json); - if (newTab === undefined) { - return; - } - newTab.tab.on("click", function() { - if (!$(this).hasClass('active')) { - AIRTIME.showbuilder.switchTab(newTab.pane, newTab.tab); - $.post(baseUrl+'playlist/edit', - {format: "json", id: newTab.pane.find(".obj_id").val(), type: newTab.pane.find(".obj_type").val()}); - } - }); - AIRTIME.playlist.init(); - - // functions in smart_blockbuilder.js - setupUI(); - appendAddButton(); - appendModAddButton(); - removeButtonCheck(); - } - function openPlaylistPanel() { viewport = AIRTIME.utilities.findViewportDimensions(); var screenWidth = Math.floor(viewport.width - 40); @@ -562,33 +440,6 @@ var AIRTIME = (function(AIRTIME){ $("#pl_edit").hide(); } - function closeTab(id) { - var curr = $(".active-tab"), - pane = id ? $(".pl-content[data-tab-id='" + id + "']") : curr, - tab = id ? $(".nav.nav-tabs [data-tab-id='" + id + "']") : $(".nav.nav-tabs .active"), - toPane = pane.next().length > 0 ? pane.next() : pane.prev(), - toTab = tab.next().length > 0 ? tab.next() : tab.prev(), - objId = pane.find(".obj_id").val(), - pl = id ? pane : $pl; - delete $openTabs[tab.attr("data-tab-type") + objId]; // Remove the closed tab from our open tabs array - - // Remove the relevant DOM elements (the tab and the tab content) - tab.remove(); - pl.remove(); - - if (pane.get(0) == curr.get(0)) { // Closing the current tab, otherwise we don't need to switch tabs - AIRTIME.showbuilder.switchTab(toPane, toTab); - } - - // If we close a tab that was causing tabs to wrap to the next row, we need to resize to change the - // margin for the tab nav - AIRTIME.playlist.onResize(); - } - - mod.closeTab = function(id) { - closeTab(id); - }; - //Purpose of this function is to iterate over all playlist elements //and verify whether they can be previewed by the browser or not. If not //then the playlist element is greyed out @@ -937,7 +788,7 @@ var AIRTIME = (function(AIRTIME){ setTimeout(function(){$status.fadeOut("slow", function(){$status.empty()})}, 5000); $pl.find(".title_obj_name").val(name); - updateActiveTabName(name); + AIRTIME.tabs.setActiveTabName(name); var $ws_id = $(".active-tab .obj_id"); $ws_id.attr("value", json.streamId); @@ -985,26 +836,21 @@ var AIRTIME = (function(AIRTIME){ $(this).unbind("click"); // Prevent repeated clicks in quick succession from closing multiple tabs var tabId = $(this).closest("li").attr("data-tab-id"); - //AIRTIME.showbuilder.switchTab($("#pl-tab-content-" + tabId), $("#pl-tab-" + tabId)); - //$pl.hide(); // 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(); - var playlistNameElem = $pl.siblings("[data-tab-id='" + tabId + "']").find('.playlist_name_display'); - var name = ""; - if (playlistNameElem.val() !== undefined) { - name = playlistNameElem.val().trim(); - } + var playlistNameElem = AIRTIME.tabs.get(tabId).find('.tab-name'); + var name = playlistNameElem.text().trim(); if ((name == $.i18n._("Untitled Playlist") || name == $.i18n._("Untitled Smart Block")) && $pl.find(".spl_sortable .spl_empty").length == 1) { mod.fnDelete(undefined, tabId); } else { - closeTab(tabId); + AIRTIME.tabs.closeTab(tabId); } $.ajax( { @@ -1041,7 +887,7 @@ var AIRTIME = (function(AIRTIME){ } else { setTitleLabel(json.name); - setTabName(json.name); + AIRTIME.tabs.setActiveTabName(json.name); mod.setModified(json.modified); if (obj_type == "block") { @@ -1176,7 +1022,7 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json", type: 'playlist'}, function(json){ - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); redrawLib(); }); }; @@ -1189,7 +1035,7 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json"}, function(json){ - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); redrawLib(); }); }; @@ -1203,13 +1049,13 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json", type: 'block'}, function(json){ - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); redrawLib(); }); }; mod.fileMdEdit = function(json) { - openFileMdEditor(json); + AIRTIME.tabs.openFileMdEditorTab(json); }; mod.fnEdit = function(id, type, url) { @@ -1219,7 +1065,7 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json", id: id, type: type}, function(json){ - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); redrawLib(); }); }; @@ -1229,7 +1075,7 @@ var AIRTIME = (function(AIRTIME){ var url, id, lastMod, type, pl = (tabId === undefined) ? $pl : $('#pl-tab-content-' + tabId); stopAudioPreview(); - id = (plid === undefined) ? getId(pl) : plid; + id = (plid === undefined) ? mod.getId(pl) : plid; lastMod = mod.getModified(pl); type = pl.find('.obj_type').val(); url = baseUrl+'playlist/delete'; @@ -1237,7 +1083,7 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json", ids: id, modified: lastMod, type: type}, function(json) { - closeTab(tabId); + AIRTIME.tabs.closeTab(tabId); redrawLib(); }); }; @@ -1246,7 +1092,7 @@ var AIRTIME = (function(AIRTIME){ var url, id, lastMod; stopAudioPreview(); - id = (wsid === undefined) ? getId() : wsid; + id = (wsid === undefined) ? mod.getId() : wsid; lastMod = mod.getModified(); type = $pl.find('.obj_type').val(); url = baseUrl+'webstream/delete'; @@ -1254,7 +1100,7 @@ var AIRTIME = (function(AIRTIME){ $.post(url, {format: "json", ids: id, modified: lastMod, type: type}, function(json){ - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); redrawLib(); }); }; @@ -1275,7 +1121,7 @@ var AIRTIME = (function(AIRTIME){ }; mod.fnOpenPlaylist = function(json) { - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); }; mod.enableUI = function() { @@ -1305,7 +1151,7 @@ var AIRTIME = (function(AIRTIME){ mod.replaceForm = function(json){ $pl.find('.editor_pane_wrapper').html(json.html); - openPlaylist(json); + AIRTIME.tabs.openPlaylistTab(json); }; @@ -1576,21 +1422,17 @@ var AIRTIME = (function(AIRTIME){ }); }; - mod.setAsActive = function() { - $pl = $(".active-tab"); - $.post(baseUrl + "playlist/change-playlist", {"id": getId(), "type": $pl.find('.obj_type').val()}); + mod.setupEventListeners = function() { + initialEvents(); + }; + + mod.setCurrent = function(pl) { + $pl = pl; }; mod.init = function() { - AIRTIME.playlist.setAsActive(); - - //$pl.delegate("#spl_delete", {"click": function(ev){ - // AIRTIME.playlist.fnDelete(); - //}}); - // - //$pl.delegate("#ws_delete", {"click": function(ev){ - // AIRTIME.playlist.fnWsDelete(); - //}}); + AIRTIME.tabs.updateActiveTab(); + if (!$pl) return; $pl.delegate(".pl-waveform-cues-btn", {"click": function(ev){ AIRTIME.playlist.showCuesWaveform(ev); diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index 9dedb1f5a..3ec1d8e8b 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -108,21 +108,6 @@ var AIRTIME = (function(AIRTIME){ } }; - mod.switchTab = function(tab, el) { - $(".active-tab").hide().removeClass("active-tab"); - tab.addClass("active-tab").show(); - - $(".nav.nav-tabs .active").removeClass("active"); - el.addClass("active"); - - if (tab.hasClass("pl-content")) { - AIRTIME.playlist.setAsActive(); - } - AIRTIME.playlist.onResize(); - AIRTIME.library.fnRedraw(); - }; - - mod.checkSelectButton = function() { var $selectable = $sbTable.find("tr"); diff --git a/airtime_mvc/public/js/airtime/showbuilder/main_builder.js b/airtime_mvc/public/js/airtime/showbuilder/main_builder.js index 6ea091420..b513db3ad 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/main_builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/main_builder.js @@ -194,7 +194,7 @@ AIRTIME = (function(AIRTIME) { $("#schedule-tab").on("click", function() { if (!$(this).hasClass('active')) { - AIRTIME.showbuilder.switchTab($("#show_builder .outer-datatable-wrapper"), $(this)); + AIRTIME.tabs.switchTab($("#show_builder .outer-datatable-wrapper"), $(this)); } }); diff --git a/airtime_mvc/public/js/airtime/showbuilder/tabs.js b/airtime_mvc/public/js/airtime/showbuilder/tabs.js new file mode 100644 index 000000000..5b1d84691 --- /dev/null +++ b/airtime_mvc/public/js/airtime/showbuilder/tabs.js @@ -0,0 +1,205 @@ +var AIRTIME = (function(AIRTIME){ + var mod, + $tabCount = 0, + $openTabs = {}, + $activeTab, + $activeTabPane; + + if (AIRTIME.tabs === undefined) { + AIRTIME.tabs = {}; + } + mod = AIRTIME.tabs; + + /* ##################################################### + Internal Functions + ##################################################### */ + + function buildNewTab(json) { + AIRTIME.library.selectNone(); + + var tabId = $openTabs[json.type + json.id]; + if (tabId !== undefined) { + mod.switchTab($("#pl-tab-content-" + tabId), $("#pl-tab-" + tabId)); + return undefined; + } + $tabCount++; + + var wrapper = "
", + t = $("#show_builder").append(wrapper).find("#pl-tab-content-" + $tabCount), + pane = $(".editor_pane_wrapper:last"), + name = json.type == "md" ? // file + pane.append(json.html).find("#track_title").val() + $.i18n._(" - Metadata Editor") + : pane.append(json.html).find(".playlist_name_display").val(), + tab = + "", + tabs = $(".nav.nav-tabs"); + + if (json.id) { + $openTabs[json.type + json.id] = $tabCount; + } + + $(".nav.nav-tabs li").removeClass("active"); + tabs.append(tab); + tabs.find("#pl-tab-" + $tabCount + " span.tab-name").text(name); + + var newTab = $("#pl-tab-" + $tabCount); + mod.switchTab(t, newTab); + + return {wrapper: pane, tab: newTab, pane: t}; + } + + function initFileMdEvents(newTab) { + newTab.tab.on("click", function() { + if (!$(this).hasClass('active')) { + mod.switchTab(newTab.pane, newTab.tab); + } + }); + + newTab.wrapper.find(".md-cancel").on("click", function() { + mod.closeTab(); + }); + + newTab.wrapper.find(".md-save").on("click", function() { + var file_id = newTab.wrapper.find('#file_id').val(), + data = newTab.wrapper.find("#edit-md-dialog form").serializeArray(); + $.post(baseUrl+'library/edit-file-md', {format: "json", id: file_id, data: data}, function() { + // don't redraw the library table if we are on calendar page + // we would be on calendar if viewing recorded file metadata + if ($("#schedule_calendar").length === 0) { + oTable.fnStandingRedraw(); + } + }); + + mod.closeTab(); + }); + + newTab.wrapper.find('#edit-md-dialog').on("keyup", function(event) { + if (event.keyCode === 13) { + newTab.wrapper.find('.md-save').click(); + } + }); + } + + /* ##################################################### + External Functions + ##################################################### */ + + mod.openFileMdEditorTab = function(json) { + var newTab = buildNewTab(json); + if (newTab === undefined) { + return; + } + + initFileMdEvents(newTab); + AIRTIME.playlist.setupEventListeners(); + }; + + mod.openPlaylistTab = function(json) { + var newTab = buildNewTab(json); + if (newTab === undefined) { + return; + } + newTab.tab.on("click", function() { + if (!$(this).hasClass('active')) { + mod.switchTab(newTab.pane, newTab.tab); + $.post(baseUrl+'playlist/edit', { + format: "json", + id: newTab.pane.find(".obj_id").val(), + type: newTab.pane.find(".obj_type").val() + }); + } + }); + AIRTIME.playlist.init(); + + // functions in smart_blockbuilder.js + setupUI(); + appendAddButton(); + appendModAddButton(); + removeButtonCheck(); + }; + + mod.closeTab = function(id) { + var pane = id ? mod.get(id, true) : $activeTabPane, + tab = id ? mod.get(id) : $activeTab, + toPane = pane.next().length > 0 ? pane.next() : pane.prev(), + toTab = tab.next().length > 0 ? tab.next() : tab.prev(), + objId = pane.find(".obj_id").val(), + contents = id ? pane : $activeTabPane; + delete $openTabs[tab.data("tab-type") + objId]; // Remove the closed tab from our open tabs array + + // Remove the relevant DOM elements (the tab and the tab content) + tab.remove(); + contents.remove(); + + if (pane.get(0) == $activeTabPane.get(0)) { // Closing the current tab, otherwise we don't need to switch tabs + mod.switchTab(toPane, toTab); + } + + // If we close a tab that was causing tabs to wrap to the next row, we need to resize to change the + // margin for the tab nav + AIRTIME.playlist.onResize(); + }; + + mod.switchTab = function(tabPane, tab) { + $activeTabPane.hide().removeClass("active-tab"); + tabPane.addClass("active-tab").show(); + + $activeTab.removeClass("active"); + tab.addClass("active"); + + mod.updateActiveTab(); + + AIRTIME.playlist.onResize(); + AIRTIME.library.fnRedraw(); + }; + + mod.setActiveTabName = function(name) { + $activeTab.find(".tab-name").text(name); + }; + + mod.updateActiveTab = function() { + $activeTabPane = $(".active-tab"); + $activeTab = $(".nav.nav-tabs .active"); + if ($activeTabPane.hasClass("pl-content")) { + mod.updatePlaylist(); + } + }; + + mod.updatePlaylist = function() { + AIRTIME.playlist.setCurrent($activeTabPane); + $.post(baseUrl + "playlist/change-playlist", { + "id": AIRTIME.playlist.getId($activeTabPane), + "type": $activeTabPane.find('.obj_type').val() + }); + }; + + mod.getActiveTab = function() { + return $activeTabPane; + }; + + mod.get = function(id, getContents) { + var allTabs = getContents ? $(".pl-content") : $(".nav.nav-tabs li"); + if (id) { + var t = null; + allTabs.each(function() { + if ($(this).data("tab-id") == id) { + t = $(this); + } + }); + // An id was passed in, but no tab with that id exists + return t; + } + return allTabs; + }; + + return AIRTIME; + +}(AIRTIME || {})); + +$(document).ready(function() { + setupTextScrolling($("#show_builder"), ".tab-name"); +}); \ No newline at end of file