mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 22:47:59 +01:00
Merge pull request #88 from Poniverse/feature-playlist-sort
Feature playlist sort
This commit is contained in:
commit
4e564aed0c
11 changed files with 263 additions and 4572 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@ Homestead.yaml
|
||||||
.env
|
.env
|
||||||
.vagrant
|
.vagrant
|
||||||
_ide_helper.php
|
_ide_helper.php
|
||||||
.idea
|
.idea
|
||||||
|
composer.lock
|
||||||
|
|
|
@ -64,10 +64,7 @@ class PlaylistsController extends ApiControllerBase
|
||||||
|
|
||||||
public function getIndex()
|
public function getIndex()
|
||||||
{
|
{
|
||||||
$page = 1;
|
$page = Input::has('page') ? Input::get('page') : 1;
|
||||||
if (Input::has('page')) {
|
|
||||||
$page = Input::get('page');
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = Playlist::summary()
|
$query = Playlist::summary()
|
||||||
->with('user',
|
->with('user',
|
||||||
|
@ -79,10 +76,11 @@ class PlaylistsController extends ApiControllerBase
|
||||||
'tracks.album',
|
'tracks.album',
|
||||||
'tracks.album.user')
|
'tracks.album.user')
|
||||||
->userDetails()
|
->userDetails()
|
||||||
->orderBy('title', 'asc')
|
|
||||||
->where('track_count', '>', 0)
|
->where('track_count', '>', 0)
|
||||||
->whereIsPublic(true);
|
->whereIsPublic(true);
|
||||||
|
|
||||||
|
$this->applyFilters($query);
|
||||||
|
|
||||||
$count = $query->count();
|
$count = $query->count();
|
||||||
$perPage = 40;
|
$perPage = 40;
|
||||||
|
|
||||||
|
@ -210,4 +208,15 @@ class PlaylistsController extends ApiControllerBase
|
||||||
|
|
||||||
return Response::json($playlists, 200);
|
return Response::json($playlists, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function applyFilters($query)
|
||||||
|
{
|
||||||
|
if (Input::has('order')) {
|
||||||
|
$order = \Input::get('order');
|
||||||
|
$parts = explode(',', $order);
|
||||||
|
$query->orderBy($parts[0], $parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ class Playlist extends Model implements Searchable, Commentable, Favouritable
|
||||||
public static function summary()
|
public static function summary()
|
||||||
{
|
{
|
||||||
return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'is_public', 'description', 'comment_count',
|
return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'is_public', 'description', 'comment_count',
|
||||||
'download_count', 'view_count', 'favourite_count');
|
'download_count', 'view_count', 'favourite_count', 'track_count');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeUserDetails($query)
|
public function scopeUserDetails($query)
|
||||||
|
|
4542
composer.lock
generated
4542
composer.lock
generated
File diff suppressed because it is too large
Load diff
17
public/templates/partials/delete-playlist-track-dialog.html
Normal file
17
public/templates/partials/delete-playlist-track-dialog.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" ng-show="title">
|
||||||
|
<button type="button" class="close" ng-click="$hide()">×</button>
|
||||||
|
<h4 class="modal-title">Remove {{ track.title }} from playlist</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete {{ track.title }}?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger" ng-click="confirmDeleteTrack();$hide()">Yes</button>
|
||||||
|
<button type="button" class="btn btn-primary" ng-click="$hide()">No</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,10 +1,31 @@
|
||||||
<div class="pagination" ng-show="totalPages > 1">
|
<ul class="dropdowns">
|
||||||
|
<li class="dropdown" ng-class="{'has-filter': !query.filters.sort.isDefault}">
|
||||||
|
<a class="dropdown-toggle btn btn-default" bs-dropdown>
|
||||||
|
Order: <strong>{{query.filters.sort.title}}</strong>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-default" pfm-eat-click ng-click="clearFilter('sort')"><i class="fa fa-remove"></i></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li ng-repeat="filter in ::filters.sort.values track by $index" ng-class="{selected: filter == query.filters.sort}">
|
||||||
|
<a pfm-eat-click href="#" ng-click="setFilter('sort', filter)">{{::filter.title}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="pagination" ng-if="totalPages > 1">
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-class="{disabled: !prevPage}"><a href="#" ng-click="gotoPage(prevPage);" pfm-eat-click>Prev</a></li>
|
<li ng-class="{disabled: !prevPage}"><a href="#" ng-click="gotoPage(prevPage);" pfm-eat-click>Prev</a></li>
|
||||||
<li ng-repeat="page in pages track by page" ng-class="{active: page == currentPage}">
|
<li ng-repeat="page in pages track by page" ng-class="{active: page == currentPage}">
|
||||||
<a href="#" ng-click="gotoPage(page);" pfm-eat-click>{{page}}</a>
|
<a href="#" ng-click="gotoPage(page);" pfm-eat-click>{{page}}</a>
|
||||||
</li>
|
</li>
|
||||||
<li ng-class="{disabled: !nextPage}"><a href="#" ng-click="gotoPage(nextPage);" pfm-eat-click>Next</a></li>
|
<li ng-class="{disabled: !nextPage}"><a href="#" ng-click="gotoPage(nextPage);" pfm-eat-click>Next</a></li>
|
||||||
|
<li class="pagination-jump">
|
||||||
|
<a href="#" ng-click="showPageSelector();" ng-hide="pageSelectorShown" pfm-eat-click>Jump…</a>
|
||||||
|
<form ng-submit="jumpToPage(inputPageNumber)" ng-show="pageSelectorShown">
|
||||||
|
<input type="number" id="pagination-jump-destination" ng-model="inputPageNumber" ng-blur="hidePageSelector()" />
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ ponyfm.config [
|
||||||
abstract: true
|
abstract: true
|
||||||
|
|
||||||
state.state 'content.playlists.list',
|
state.state 'content.playlists.list',
|
||||||
url: '?page'
|
url: '^/playlists?filter&page'
|
||||||
controller: 'playlists-list'
|
controller: 'playlists-list'
|
||||||
templateUrl: '/templates/playlists/list.html'
|
templateUrl: '/templates/playlists/list.html'
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,17 @@
|
||||||
window.pfm.preloaders['playlists-list'] = [
|
window.pfm.preloaders['playlists-list'] = [
|
||||||
'playlists', '$state'
|
'playlists', '$state'
|
||||||
(playlists, $state) ->
|
(playlists, $state) ->
|
||||||
playlists.fetchList($state.params.page, true)
|
playlists.loadFilters().then(->
|
||||||
|
playlists.mainQuery.fromFilterString($state.params.filter)
|
||||||
|
playlists.mainQuery.setPage $state.params.page || 1
|
||||||
|
|
||||||
|
playlists.mainQuery.fetch()
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = angular.module('ponyfm').controller "playlists-list", [
|
module.exports = angular.module('ponyfm').controller "playlists-list", [
|
||||||
'$scope', 'playlists', '$state',
|
'$scope', 'playlists', '$state',
|
||||||
($scope, playlists, $state) ->
|
($scope, playlists, $state) ->
|
||||||
playlists.fetchList($state.params.page).done (searchResults) ->
|
playlists.mainQuery.fetch().done (searchResults) ->
|
||||||
$scope.playlists = searchResults.playlists
|
$scope.playlists = searchResults.playlists
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,25 +14,69 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
window.pfm.preloaders['playlists'] = [
|
||||||
|
'playlists', '$state'
|
||||||
|
(playlists) ->
|
||||||
|
playlists.loadFilters()
|
||||||
|
]
|
||||||
|
|
||||||
module.exports = angular.module('ponyfm').controller "playlists", [
|
module.exports = angular.module('ponyfm').controller "playlists", [
|
||||||
'$scope', 'playlists', '$state'
|
'$scope', 'playlists', '$state'
|
||||||
($scope, playlists, $state) ->
|
($scope, playlists, $state) ->
|
||||||
|
|
||||||
refreshPages = (list) ->
|
$scope.query = playlists.mainQuery
|
||||||
$scope.playlists = list.playlists
|
$scope.filters = playlists.filters
|
||||||
$scope.currentPage = parseInt(list.current_page)
|
|
||||||
$scope.totalPages = parseInt(list.total_pages)
|
|
||||||
|
|
||||||
|
$scope.setFilter = (filter, value) ->
|
||||||
|
$scope.query.setFilter filter, value
|
||||||
|
$state.transitionTo 'content.playlists.list', {filter: $scope.query.toFilterString()}
|
||||||
|
|
||||||
|
$scope.clearFilter = (filter) ->
|
||||||
|
$scope.query.clearFilter filter
|
||||||
|
$state.transitionTo 'content.playlists.list', {filter: $scope.query.toFilterString()}
|
||||||
|
|
||||||
|
playlists.mainQuery.listen (searchResults) ->
|
||||||
|
$scope.playlists = searchResults.playlists
|
||||||
|
$scope.currentPage = parseInt(searchResults.current_page)
|
||||||
|
$scope.totalPages = parseInt(searchResults.total_pages)
|
||||||
delete $scope.nextPage
|
delete $scope.nextPage
|
||||||
delete $scope.prevPage
|
delete $scope.prevPage
|
||||||
|
|
||||||
$scope.nextPage = $scope.currentPage + 1 if $scope.currentPage < $scope.totalPages
|
$scope.nextPage = $scope.currentPage + 1 if $scope.currentPage < $scope.totalPages
|
||||||
$scope.prevPage = $scope.currentPage - 1 if $scope.currentPage > 1
|
$scope.prevPage = $scope.currentPage - 1 if $scope.currentPage > 1
|
||||||
$scope.pages = [1..$scope.totalPages]
|
$scope.allPages = [1..$scope.totalPages]
|
||||||
|
|
||||||
playlists.fetchList($state.params.page).done refreshPages
|
# TODO: turn this into a directive
|
||||||
$scope.$on 'playlists-feteched', (e, list) -> refreshPages(list)
|
# The actual first page will always be in the paginator.
|
||||||
|
$scope.pages = [1]
|
||||||
|
|
||||||
|
# This logic determines how many pages to add prior to the current page, if any.
|
||||||
|
firstPage = Math.max(2, $scope.currentPage-3)
|
||||||
|
$scope.pages = $scope.pages.concat [firstPage..$scope.currentPage] unless $scope.currentPage == 1
|
||||||
|
|
||||||
|
pagesLeftToAdd = 8-$scope.pages.length
|
||||||
|
|
||||||
|
lastPage = Math.min($scope.totalPages - 1, $scope.currentPage+1+pagesLeftToAdd)
|
||||||
|
$scope.pages = $scope.pages.concat([$scope.currentPage+1..lastPage]) unless $scope.currentPage >= lastPage
|
||||||
|
|
||||||
|
# The actual last page will always be in the paginator.
|
||||||
|
$scope.pages.push($scope.totalPages) unless $scope.totalPages in $scope.pages
|
||||||
|
|
||||||
|
$scope.pageSelectorShown = false
|
||||||
|
|
||||||
$scope.gotoPage = (page) ->
|
$scope.gotoPage = (page) ->
|
||||||
return if !page
|
$state.transitionTo 'content.playlists.list', {filter: $state.params.filter, page: page}
|
||||||
$state.transitionTo 'content.playlists.list', {page: page}
|
|
||||||
|
$scope.showPageSelector = () ->
|
||||||
|
$scope.pageSelectorShown = true
|
||||||
|
focus('#pagination-jump-destination')
|
||||||
|
|
||||||
|
$scope.hidePageSelector = () ->
|
||||||
|
$scope.pageSelectorShown = false
|
||||||
|
|
||||||
|
|
||||||
|
$scope.jumpToPage = (inputPageNumber) ->
|
||||||
|
$scope.gotoPage(inputPageNumber)
|
||||||
|
|
||||||
|
$scope.$on '$destroy', -> playlists.mainQuery = playlists.createQuery()
|
||||||
]
|
]
|
||||||
|
|
|
@ -32,16 +32,16 @@ module.exports = angular.module('ponyfm').directive 'pfmTracksList', () ->
|
||||||
$scope.playlist and $scope.auth.isLogged and $scope.playlist.user.id == $scope.auth.user.id
|
$scope.playlist and $scope.auth.isLogged and $scope.playlist.user.id == $scope.auth.user.id
|
||||||
|
|
||||||
$scope.removeFromPlaylist = (track) ->
|
$scope.removeFromPlaylist = (track) ->
|
||||||
#$dialog.messageBox "Remove #{track.title} from playlist",
|
$scope.track = track
|
||||||
# "Are you sure you want to delete \"#{track.title}\"?", [
|
dialog = $modal
|
||||||
# { result: 'ok', label: 'Yes', cssClass: 'btn-danger' },
|
templateUrl: '/templates/partials/delete-playlist-track-dialog.html'
|
||||||
# { result: 'cancel', label: 'No', cssClass: 'btn-primary' }
|
scope: $scope,
|
||||||
# ]
|
show: true
|
||||||
#.open().then (res) ->
|
|
||||||
# return if res is 'cancel'
|
$scope.confirmDeleteTrack = () ->
|
||||||
# playlists.removeTrackFromPlaylist $scope.playlist?.id, track.id
|
playlists.removeTrackFromPlaylist $scope.playlist?.id, $scope.track.id
|
||||||
# .done ->
|
.done ->
|
||||||
# $scope.tracks = _.reject $scope.tracks, (t) -> t.id == track.id
|
$scope.tracks = _.reject $scope.tracks, (t) -> t.id == $scope.track.id
|
||||||
|
|
||||||
$scope.toggleFavourite = (track) ->
|
$scope.toggleFavourite = (track) ->
|
||||||
favourites.toggle('track', track.id).done (res) ->
|
favourites.toggle('track', track.id).done (res) ->
|
||||||
|
|
|
@ -18,11 +18,146 @@ module.exports = angular.module('ponyfm').factory('playlists', [
|
||||||
'$rootScope', '$state', '$http', 'auth'
|
'$rootScope', '$state', '$http', 'auth'
|
||||||
($rootScope, $state, $http, auth) ->
|
($rootScope, $state, $http, auth) ->
|
||||||
playlistDef = null
|
playlistDef = null
|
||||||
|
filterDef = null
|
||||||
playlists = {}
|
playlists = {}
|
||||||
playlistPages = []
|
playlistPages = []
|
||||||
|
|
||||||
|
class Query
|
||||||
|
cacheDef: null
|
||||||
|
page: 1
|
||||||
|
listeners: []
|
||||||
|
|
||||||
|
constructor: (@availableFilters) ->
|
||||||
|
@filters = {}
|
||||||
|
@hasLoadedFilters = false
|
||||||
|
@resetFilters()
|
||||||
|
|
||||||
|
resetFilters: ->
|
||||||
|
_.each @availableFilters, (filter, name) =>
|
||||||
|
if filter.type == 'single'
|
||||||
|
@filters[name] = _.find filter.values, (f) -> f.isDefault
|
||||||
|
else
|
||||||
|
@filters[name] = {title: 'Any', selectedArray: [], selectedObject: {}}
|
||||||
|
|
||||||
|
clearFilter: (type) ->
|
||||||
|
@cachedDef = null
|
||||||
|
@page = 1
|
||||||
|
filter = @availableFilters[type]
|
||||||
|
|
||||||
|
if filter.type == 'single'
|
||||||
|
@filters[type] = _.find filter.values, (f) -> f.isDefault
|
||||||
|
else
|
||||||
|
currentFilter = @filters[type]
|
||||||
|
currentFilter.selectedArray = []
|
||||||
|
currentFilter.selectedObject = {}
|
||||||
|
currentFilter.title = 'Any'
|
||||||
|
|
||||||
|
setPage: (page) ->
|
||||||
|
@page = page
|
||||||
|
@cachedDef = null
|
||||||
|
|
||||||
|
setFilter: (type, value) ->
|
||||||
|
@cachedDef = null
|
||||||
|
@page = 1
|
||||||
|
@filters[type] = value
|
||||||
|
|
||||||
|
toFilterString: ->
|
||||||
|
parts = []
|
||||||
|
_.each @availableFilters, (filter, name) =>
|
||||||
|
filterName = filter.name
|
||||||
|
if filter.type == 'single'
|
||||||
|
return if @filters[name].query == ''
|
||||||
|
parts.push(filterName + '-' + @filters[name].query)
|
||||||
|
else
|
||||||
|
return if @filters[name].selectedArray.length == 0
|
||||||
|
parts.push(filterName + '-' + _.map(@filters[name].selectedArray, (f) -> f.id).join '-')
|
||||||
|
|
||||||
|
return parts.join '!'
|
||||||
|
|
||||||
|
fromFilterString: (str) ->
|
||||||
|
@hasLoadedFilters = true
|
||||||
|
@cachedDef = null
|
||||||
|
@resetFilters()
|
||||||
|
|
||||||
|
filters = (str || "").split '!'
|
||||||
|
for queryFilter in filters
|
||||||
|
parts = queryFilter.split '-'
|
||||||
|
queryName = parts[0]
|
||||||
|
|
||||||
|
filterName = null
|
||||||
|
filter = null
|
||||||
|
|
||||||
|
for name,f of @availableFilters
|
||||||
|
continue if f.name != queryName
|
||||||
|
filterName = name
|
||||||
|
filter = f
|
||||||
|
|
||||||
|
return if !filter
|
||||||
|
|
||||||
|
if filter.type == 'single'
|
||||||
|
filterToSet = _.find filter.values, (f) -> f.query == parts[1]
|
||||||
|
filterToSet = (_.find filter.values, (f) -> f.isDefault) if filterToSet == null
|
||||||
|
@setFilter filterName, filterToSet
|
||||||
|
else
|
||||||
|
@toggleListFilter filterName, id for id in _.rest parts, 1
|
||||||
|
|
||||||
|
listen: (listener) ->
|
||||||
|
@listeners.push listener
|
||||||
|
@cachedDef.done listener if @cachedDef
|
||||||
|
|
||||||
|
fetch: () ->
|
||||||
|
return @cachedDef if @cachedDef
|
||||||
|
@cachedDef = new $.Deferred()
|
||||||
|
playlistDef = @cachedDef
|
||||||
|
|
||||||
|
query = '/api/web/playlists?'
|
||||||
|
|
||||||
|
parts = ['page=' + @page]
|
||||||
|
_.each @availableFilters, (filter, name) =>
|
||||||
|
if filter.type == 'single'
|
||||||
|
parts.push @filters[name].filter
|
||||||
|
else
|
||||||
|
queryName = filter.filterName
|
||||||
|
for item in @filters[name].selectedArray
|
||||||
|
parts.push queryName + "[]=" + item.id
|
||||||
|
|
||||||
|
query += parts.join '&'
|
||||||
|
$http.get(query).success (playlists) =>
|
||||||
|
@playlists = playlists
|
||||||
|
for listener in @listeners
|
||||||
|
listener playlists
|
||||||
|
|
||||||
|
playlistDef.resolve playlists
|
||||||
|
|
||||||
|
playlistDef.promise()
|
||||||
|
|
||||||
|
|
||||||
self =
|
self =
|
||||||
pinnedPlaylists: []
|
pinnedPlaylists: []
|
||||||
|
filters: {}
|
||||||
|
|
||||||
|
createQuery: -> new Query self.filters
|
||||||
|
|
||||||
|
loadFilters: ->
|
||||||
|
return filterDef if filterDef
|
||||||
|
|
||||||
|
filterDef = new $.Deferred()
|
||||||
|
self.filters.sort =
|
||||||
|
type: 'single'
|
||||||
|
name: 'sort'
|
||||||
|
values: [
|
||||||
|
{title: 'Most Favourited', query: 'favourites', isDefault: true, filter: 'order=favourite_count,desc'}
|
||||||
|
{title: 'Most Viewed', query: 'plays', isDefault: false, filter: 'order=view_count,desc'},
|
||||||
|
{title: 'Most Downloaded', query: 'downloads', isDefault: false, filter: 'order=download_count,desc'},
|
||||||
|
{title: 'Alphabetical', query: 'alphabetical', isDefault: false, filter: 'order=title,asc'},
|
||||||
|
{title: 'Latest', query: 'latest', isDefault: false, filter: 'order=created_at,desc'},
|
||||||
|
{title: 'Track count', query: 'tracks', isDefault: false, filter: 'order=track_count,desc'},
|
||||||
|
]
|
||||||
|
|
||||||
|
self.mainQuery = self.createQuery()
|
||||||
|
filterDef.resolve self
|
||||||
|
|
||||||
|
filterDef.promise()
|
||||||
|
|
||||||
fetchList: (page, force) ->
|
fetchList: (page, force) ->
|
||||||
force = force || false
|
force = force || false
|
||||||
|
@ -148,6 +283,7 @@ module.exports = angular.module('ponyfm').factory('playlists', [
|
||||||
|
|
||||||
def
|
def
|
||||||
|
|
||||||
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
self
|
self
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in a new issue