* SAAS-1153 - more work on station podcast frontend. Add delete and edit button functionality for episode table

* Various fixes and backend updates
* Move station podcast creation to id getter in Preferences
This commit is contained in:
Duncan Sommerville 2015-11-03 16:23:17 -05:00
parent c0d8b8b39c
commit 22f8b0f328
16 changed files with 102 additions and 72 deletions

View file

@ -42,23 +42,14 @@ class IndexController extends Zend_Controller_Action
//station feed episodes //station feed episodes
$stationPodcastId = Application_Model_Preference::getStationPodcastId(); $stationPodcastId = Application_Model_Preference::getStationPodcastId();
if (!empty($stationPodcastId)) {
$podcastEpisodesService = new Application_Service_PodcastEpisodeService(); $podcastEpisodesService = new Application_Service_PodcastEpisodeService();
$episodes = $podcastEpisodesService->getPodcastEpisodes($stationPodcastId); $episodes = $podcastEpisodesService->getPodcastEpisodes($stationPodcastId);
foreach ($episodes as $e => $v) { foreach ($episodes as $e => $v) {
$episodes[$e]["track_metadata"]["track_title"] = htmlspecialchars($v["track_metadata"]["track_title"], ENT_QUOTES); $episodes[$e]["CcFiles"]["track_title"] = htmlspecialchars($v["CcFiles"]["track_title"], ENT_QUOTES);
$episodes[$e]["track_metadata"]["artist_name"] = htmlspecialchars($v["track_metadata"]["artist_name"], ENT_QUOTES); $episodes[$e]["CcFiles"]["artist_name"] = htmlspecialchars($v["CcFiles"]["artist_name"], ENT_QUOTES);
}
} else {
// Station podcast does not exist yet
// (creation is implicitly done when a new podcast is created in the dashboard)
// return empty list of episodes
$episodes = [];
} }
$this->view->episodes = json_encode($episodes); $this->view->episodes = json_encode($episodes);
$this->view->displayRssTab = (!Application_Model_Preference::getStationPodcastPrivacy()); $this->view->displayRssTab = (!Application_Model_Preference::getStationPodcastPrivacy());
} }

View file

@ -120,7 +120,7 @@ class LocaleController extends Zend_Controller_Action
"Input must be a number" => _("Input must be a number"), "Input must be a number" => _("Input must be a number"),
"Input must be in the format: yyyy-mm-dd" => _("Input must be in the format: yyyy-mm-dd"), "Input must be in the format: yyyy-mm-dd" => _("Input must be in the format: yyyy-mm-dd"),
"Input must be in the format: hh:mm:ss.t" => _("Input must be in the format: hh:mm:ss.t"), "Input must be in the format: hh:mm:ss.t" => _("Input must be in the format: hh:mm:ss.t"),
"My Station Podcast" => _("My Station Podcast"), "Station Podcast" => _("Station Podcast"),
//library/plupload.js //library/plupload.js
"You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?" "You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"
=> _("You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"), => _("You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"),

View file

@ -49,7 +49,7 @@ class Application_Form_EditAudioMD extends Zend_Form
$this->addElement($album_title); $this->addElement($album_title);
// Description field // Description field
$description = new Zend_Form_Element_Text('description'); $description = new Zend_Form_Element_Textarea('description');
$description->class = 'input_text'; $description->class = 'input_text';
$description->setLabel(_('Description:')) $description->setLabel(_('Description:'))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))

View file

@ -1518,7 +1518,12 @@ class Application_Model_Preference
public static function getStationPodcastId() public static function getStationPodcastId()
{ {
return self::getValue("station_podcast_id"); // Create the Station podcast if it doesn't exist.
$stationPodcastId = self::getValue("station_podcast_id");
if (empty($stationPodcastId)) {
$stationPodcastId = Application_Service_PodcastService::createStationPodcast();
}
return $stationPodcastId;
} }
public static function setStationPodcastId($value) public static function setStationPodcastId($value)

View file

@ -28,19 +28,12 @@ class Rest_PodcastController extends Zend_Rest_Controller
$sortDir = $this->_getParam('sort_dir', Criteria::ASC); $sortDir = $this->_getParam('sort_dir', Criteria::ASC);
$stationPodcastId = Application_Model_Preference::getStationPodcastId(); $stationPodcastId = Application_Model_Preference::getStationPodcastId();
if (!empty($stationPodcastId)) {
$query = PodcastQuery::create() $query = PodcastQuery::create()
// Don't return the Station podcast - we fetch it separately // Don't return the Station podcast - we fetch it separately
->filterByDbId($stationPodcastId, Criteria::NOT_EQUAL) ->filterByDbId($stationPodcastId, Criteria::NOT_EQUAL)
->setLimit($limit) ->setLimit($limit)
->setOffset($offset) ->setOffset($offset)
->orderBy($sortColumn, $sortDir); ->orderBy($sortColumn, $sortDir);
} else {
$query = PodcastQuery::create()
->setLimit($limit)
->setOffset($offset)
->orderBy($sortColumn, $sortDir);
}
$queryResult = $query->find(); $queryResult = $query->find();
@ -89,7 +82,7 @@ class Rest_PodcastController extends Zend_Rest_Controller
try { try {
//$requestData = json_decode($this->getRequest()->getRawBody(), true); //$requestData = json_decode($this->getRequest()->getRawBody(), true);
$requestData = $this->getRequest()->getPost(); $requestData = $this->getRequest()->getPost();
$podcast = PodcastFactory::create($requestData["url"]); $podcast = Application_Service_PodcastService::createFromFeedUrl($requestData["url"]);
$path = 'podcast/podcast.phtml'; $path = 'podcast/podcast.phtml';
$this->view->podcast = $podcast; $this->view->podcast = $podcast;

View file

@ -236,15 +236,15 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
} }
$sortDir = ($sortDir === "DESC") ? $sortDir = Criteria::DESC : Criteria::ASC; $sortDir = ($sortDir === "DESC") ? $sortDir = Criteria::DESC : Criteria::ASC;
$isStationPodcast = $podcastId === Application_Model_Preference::getStationPodcastId(); $isStationPodcast = $podcastId == Application_Model_Preference::getStationPodcastId();
$episodes = PodcastEpisodesQuery::create() $episodes = PodcastEpisodesQuery::create()
->joinWithCcFiles('files')
->filterByDbPodcastId($podcastId); ->filterByDbPodcastId($podcastId);
if ($isStationPodcast) { if ($isStationPodcast) {
$episodes = $episodes->setLimit($limit); $episodes = $episodes->setLimit($limit);
} }
$episodes = $episodes->setOffset($offset) $episodes = $episodes->joinWith('PodcastEpisodes.CcFiles')
->setOffset($offset)
->orderBy($sortColumn, $sortDir) ->orderBy($sortColumn, $sortDir)
->find(); ->find();
@ -257,7 +257,6 @@ class Application_Service_PodcastEpisodeService extends Application_Service_Thir
foreach ($episodes as $episode) { foreach ($episodes as $episode) {
/** @var PodcastEpisodes $episode */ /** @var PodcastEpisodes $episode */
$episodeArr = $episode->toArray(BasePeer::TYPE_FIELDNAME, true, [], true); $episodeArr = $episode->toArray(BasePeer::TYPE_FIELDNAME, true, [], true);
Logging::info($episodeArr);
array_push($episodesArray, $episodeArr); array_push($episodesArray, $episodeArr);
} }
return $episodesArray; return $episodesArray;

View file

@ -161,6 +161,7 @@ class Application_Service_PodcastService
// Set the download key when we create the station podcast // Set the download key when we create the station podcast
// The value is randomly generated in the setter // The value is randomly generated in the setter
Application_Model_Preference::setStationPodcastDownloadKey(); Application_Model_Preference::setStationPodcastDownloadKey();
return $podcast->getDbId();
} }
//TODO move this somewhere where it makes sense //TODO move this somewhere where it makes sense
@ -243,6 +244,7 @@ class Application_Service_PodcastService
if ($podcast) { if ($podcast) {
$podcast->delete(); $podcast->delete();
// FIXME: I don't think we should be able to delete the station podcast...
if ($podcastId == Application_Model_Preference::getStationPodcastId()) { if ($podcastId == Application_Model_Preference::getStationPodcastId()) {
Application_Model_Preference::setStationPodcastId(null); Application_Model_Preference::setStationPodcastId(null);
} }

View file

@ -7,7 +7,7 @@ class Application_Service_PublishService {
*/ */
private static $SOURCES = array( private static $SOURCES = array(
"soundcloud" => SOUNDCLOUD, "soundcloud" => SOUNDCLOUD,
"station_podcast" => "My Station Podcast" "station_podcast" => "Station Podcast"
); );
/** /**

View file

@ -59,15 +59,16 @@ document.getElementById(id).width= (newwidth) + "px";
$.each(<?php echo $this->episodes ?>, function(index, value){ $.each(<?php echo $this->episodes ?>, function(index, value){
// map mime to format muses recognizes // map mime to format muses recognizes
// TODO: this doesn't make a difference // TODO: this doesn't make a difference
if (value.track_metadata.mime == "audio/mp3") { var metadata = value.CcFiles;
value.track_metadata.mime = "mp3"; if (metadata.mime == "audio/mp3") {
} else if (value.track_metadata.mime == "audio/vorbis") { metadata.mime = "mp3";
value.track_metadata.mime = "ogg"; } else if (metadata.mime == "audio/vorbis") {
metadata.mime = "ogg";
} }
$("#tab-4").append("<div>"+value.track_metadata.artist_name+" - "+value.track_metadata.track_title+ $("#tab-4").append("<div>"+metadata.artist_name+" - "+metadata.track_title+
" <a id='rss-download-link' href='"+value.download_url+"'>Download</a>" + " <a id='rss-download-link' href='"+value.download_url+"'>Download</a>" +
" <a id='rss-play-link' data-metaartist='"+value.track_metadata.artist_name+"' data-metatitle='"+value.track_metadata.track_title+"' data-streamurl='"+value.download_url+"' data-streamcodec='"+value.track_metadata.mime+"' href='#'>Play</a></div>"); " <a id='rss-play-link' data-metaartist='"+metadata.artist_name+"' data-metatitle='"+metadata.track_title+"' data-streamurl='"+value.download_url+"' data-streamcodec='"+metadata.mime+"' href='#'>Play</a></div>");
}); });
$("a#rss-play-link").click(function() { $("a#rss-play-link").click(function() {

View file

@ -7,7 +7,7 @@
<button class="btn"><?php echo _("View Feed") ?></button> <button class="btn"><?php echo _("View Feed") ?></button>
</a> </a>
</div> </div>
<div class="inner_editor_wrapper"> <div class="inner_editor_wrapper station_podcast_wrapper">
<form class="podcast-metadata"> <form class="podcast-metadata">
<p> <p>
<?php echo _("Check out the ") ?><a target="_blank" href="http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements"><?php echo _("RSS specification") ?></a> <?php echo _("Check out the ") ?><a target="_blank" href="http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements"><?php echo _("RSS specification") ?></a>

View file

@ -3972,7 +3972,7 @@ li .ui-state-hover {
margin: 5px 10px 0 0; margin: 5px 10px 0 0;
} }
.angular_wrapper .inner_editor_wrapper { .angular_wrapper > .station_podcast_wrapper {
flex: 1 100%; flex: 1 100%;
} }

View file

@ -1159,7 +1159,7 @@ var AIRTIME = (function(AIRTIME) {
if (oItems.publish !== undefined) { if (oItems.publish !== undefined) {
if (data.ftype === "audioclip") { if (data.ftype === "audioclip") {
callback = function() { callback = function() {
AIRTIME.publish.publishTrack(data.id); AIRTIME.publish.openPublishDialog(data.id);
}; };
} }
oItems.publish.callback = callback; oItems.publish.callback = callback;
@ -1264,7 +1264,7 @@ var AIRTIME = (function(AIRTIME) {
podcastToolbarButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.DELETE].eventHandlers.click = AIRTIME.podcast.deleteSelectedPodcasts; podcastToolbarButtons[AIRTIME.widgets.Table.TOOLBAR_BUTTON_ROLES.DELETE].eventHandlers.click = AIRTIME.podcast.deleteSelectedPodcasts;
// Add a button to view the station podcast // Add a button to view the station podcast
podcastToolbarButtons["StationPodcast"] = { podcastToolbarButtons["StationPodcast"] = {
title : $.i18n._("My Station Podcast"), title : $.i18n._("Station Podcast"),
iconClass : "icon-music", iconClass : "icon-music",
extraBtnClass : "btn-small", extraBtnClass : "btn-small",
elementId : "", elementId : "",

View file

@ -7,7 +7,7 @@ var AIRTIME = (function (AIRTIME) {
mod = AIRTIME.podcast; mod = AIRTIME.podcast;
var endpoint = 'rest/podcast/', PodcastTable; var endpoint = 'rest/podcast/', PodcastTable, $stationPodcastTab;
/** /**
* PodcastController constructor. * PodcastController constructor.
@ -69,7 +69,7 @@ var AIRTIME = (function (AIRTIME) {
* *
* Save each of the selected episodes and update the podcast object. * Save each of the selected episodes and update the podcast object.
*/ */
$scope.savePodcast = $scope.savePodcast || function () { $scope.savePodcast = function () {
var episodes = self.episodeTable.getSelectedRows(); var episodes = self.episodeTable.getSelectedRows();
// TODO: Should we implement a batch endpoint for this instead? // TODO: Should we implement a batch endpoint for this instead?
jQuery.each(episodes, function () { jQuery.each(episodes, function () {
@ -92,6 +92,8 @@ var AIRTIME = (function (AIRTIME) {
self.$scope = $scope; self.$scope = $scope;
self.$http = $http; self.$http = $http;
self.initialize(); self.initialize();
return self;
} }
/** /**
@ -154,7 +156,8 @@ var AIRTIME = (function (AIRTIME) {
*/ */
function StationPodcastController($scope, $http, podcast, tab) { function StationPodcastController($scope, $http, podcast, tab) {
// Super call to parent controller // Super call to parent controller
PodcastController.call(this, $scope, $http, podcast, tab); var self = PodcastController.call(this, $scope, $http, podcast, tab);
$stationPodcastTab = tab;
/** /**
* For the station podcast. * For the station podcast.
@ -166,7 +169,23 @@ var AIRTIME = (function (AIRTIME) {
}; };
$scope.deleteSelectedEpisodes = function () { $scope.deleteSelectedEpisodes = function () {
// TODO var episodes = self.episodeTable.getSelectedRows();
jQuery.each(episodes, function () {
$http.delete(endpoint + $scope.podcast.id + '/episodes/' + this.id + '?csrf_token=' + $scope.csrf)
.success(function () {
self.reloadEpisodeTable();
});
});
};
$scope.openSelectedTabEditors = function () {
var episodes = self.episodeTable.getSelectedRows();
$.each(episodes, function () {
var uid = AIRTIME.library.MediaTypeStringEnum.FILE + "_" + this.file_id;
$.get(baseUrl + "library/edit-file-md/id/" + this.file_id, {format: "json"}, function (json) {
AIRTIME.playlist.fileMdEdit(json, uid);
});
});
}; };
} }
@ -185,15 +204,29 @@ var AIRTIME = (function (AIRTIME) {
StationPodcastController.prototype._initTable = function() { StationPodcastController.prototype._initTable = function() {
var $scope = this.$scope, var $scope = this.$scope,
buttons = { buttons = {
editBtn: {
title : $.i18n._('Edit'),
iconClass : 'icon-pencil',
extraBtnClass : '',
elementId : '',
eventHandlers : {
click: function(e) {
$scope.openSelectedTabEditors();
}
}
},
deleteBtn: { deleteBtn: {
title : $.i18n._('Delete'), title : $.i18n._('Delete'),
iconClass : 'icon-trash', iconClass : 'icon-trash',
extraBtnClass : 'btn-small btn-danger', extraBtnClass : 'btn-danger',
elementId : '', elementId : '',
eventHandlers : { eventHandlers : {
click: $scope.deleteSelectedEpisodes click: function(e) {
$scope.deleteSelectedEpisodes();
} }
} }
}
}, },
params = { params = {
sAjaxSource : endpoint + $scope.podcast.id + '/episodes', sAjaxSource : endpoint + $scope.podcast.id + '/episodes',
@ -231,12 +264,13 @@ var AIRTIME = (function (AIRTIME) {
* *
* Bulk methods use a POST request because we need to send data in the request body. * 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 method HTTP request method type
* @param callback function to run upon success * @param callback function to run upon success
* @private * @private
*/ */
function _bulkAction(method, callback) { function _bulkAction(selectedData, method, callback) {
var ids = [], selectedData = AIRTIME.library.podcastTableWidget.getSelectedRows(); var ids = [];
selectedData.forEach(function(el) { selectedData.forEach(function(el) {
var uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+el.id, var uid = AIRTIME.library.MediaTypeStringEnum.PODCAST+"_"+el.id,
t = AIRTIME.tabs.get(uid); t = AIRTIME.tabs.get(uid);
@ -357,16 +391,20 @@ var AIRTIME = (function (AIRTIME) {
* Open a tab to view and edit the station podcast. * Open a tab to view and edit the station podcast.
*/ */
mod.openStationPodcast = function() { mod.openStationPodcast = function() {
if (typeof $stationPodcastTab === 'undefined') {
$.get(endpoint + 'station', function(json) { $.get(endpoint + 'station', function(json) {
_initAppFromResponse(json); _initAppFromResponse(json);
}) });
} else if ($stationPodcastTab != AIRTIME.tabs.getActiveTab()) {
$stationPodcastTab.switchTo();
}
}; };
/** /**
* Create a bulk request to edit all currently selected podcasts. * Create a bulk request to edit all currently selected podcasts.
*/ */
mod.editSelectedPodcasts = function() { mod.editSelectedPodcasts = function() {
_bulkAction(HTTPMethods.GET, function(json) { _bulkAction(AIRTIME.library.podcastTableWidget.getSelectedRows(), HTTPMethods.GET, function(json) {
json.forEach(function(data) { json.forEach(function(data) {
_initAppFromResponse(data); _initAppFromResponse(data);
}); });
@ -378,7 +416,7 @@ var AIRTIME = (function (AIRTIME) {
*/ */
mod.deleteSelectedPodcasts = function() { mod.deleteSelectedPodcasts = function() {
if (confirm($.i18n._("Are you sure you want to delete the selected podcasts from your library?"))) { if (confirm($.i18n._("Are you sure you want to delete the selected podcasts from your library?"))) {
_bulkAction(HTTPMethods.DELETE, function () { _bulkAction(AIRTIME.library.podcastTableWidget.getSelectedRows(), HTTPMethods.DELETE, function () {
AIRTIME.library.podcastDataTable.fnDraw(); AIRTIME.library.podcastDataTable.fnDraw();
}); });
} }

View file

@ -121,7 +121,7 @@ var AIRTIME = (function (AIRTIME) {
}; };
mod.publishTrack = function(mediaId) { mod.openPublishDialog = function(mediaId) {
jQuery.get(dialogUrl, { csrf_token: jQuery("#csrf").val() }) jQuery.get(dialogUrl, { csrf_token: jQuery("#csrf").val() })
.success(function(html) { .success(function(html) {

View file

@ -1051,11 +1051,12 @@ var AIRTIME = (function(AIRTIME){
}); });
newTab.contents.find(".md-publish").on("click", function() { newTab.contents.find(".md-publish").on("click", function() {
AIRTIME.publish.publishTrack(fileId); AIRTIME.publish.openPublishDialog(fileId);
}); });
newTab.wrapper.find('.edit-md-dialog').on("keyup", function(event) { newTab.wrapper.find('.edit-md-dialog').on("keyup", function(event) {
if (event.keyCode === 13) { // Don't submit if the user hits enter in a textarea (description)
if ($(event.target).is('input') && event.keyCode === 13) {
newTab.wrapper.find('.md-save').click(); newTab.wrapper.find('.md-save').click();
} }
}); });

View file

@ -87,7 +87,7 @@ var AIRTIME = (function(AIRTIME) {
} }
self._datatable = self._$wrapperDOMNode.dataTable(options); self._datatable = self._$wrapperDOMNode.dataTable(options);
self._datatable.fnDraw(); //Load the AJAX data now that our event handlers have been bound. // self._datatable.fnDraw(); //Load the AJAX data now that our event handlers have been bound.
self._setupEventHandlers(bItemSelection); self._setupEventHandlers(bItemSelection);
//return self._datatable; //return self._datatable;