Track searching stuff
|
@ -6,14 +6,15 @@
|
|||
use Entities\License;
|
||||
use Entities\ShowSong;
|
||||
use Entities\TrackType;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TaxonomiesController extends \ApiControllerBase {
|
||||
public function getAll() {
|
||||
return \Response::json([
|
||||
'licenses' => License::all()->toArray(),
|
||||
'genres' => Genre::orderBy('name')->get()->toArray(),
|
||||
'track_types' => TrackType::all()->toArray(),
|
||||
'show_songs' => ShowSong::select('title', 'id', 'slug')->get()->toArray()
|
||||
'genres' => Genre::select('genres.*', DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.genre_id = genres.id AND tracks.published_at IS NOT NULL) AS track_count'))->orderBy('name')->get()->toArray(),
|
||||
'track_types' => TrackType::select('track_types.*', DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.track_type_id = track_types.id AND tracks.published_at IS NOT NULL) AS track_count'))->get()->toArray(),
|
||||
'show_songs' => ShowSong::select('title', 'id', 'slug', DB::raw('(SELECT COUNT(tracks.id) FROM show_song_track INNER JOIN tracks ON tracks.id = show_song_track.track_id WHERE show_song_track.show_song_id = show_songs.id AND tracks.published_at IS NOT NULL) AS track_count'))->get()->toArray()
|
||||
], 200);
|
||||
}
|
||||
}
|
|
@ -34,39 +34,32 @@
|
|||
$tracks = [];
|
||||
|
||||
foreach ($query->get() as $track) {
|
||||
$tracks[] = [
|
||||
'id' => $track->id,
|
||||
'title' => $track->title,
|
||||
'user' => [
|
||||
'id' => $track->user->id,
|
||||
'name' => $track->user->display_name,
|
||||
'url' => $track->user->url
|
||||
],
|
||||
'url' => $track->url,
|
||||
'slug' => $track->slug,
|
||||
'is_vocal' => $track->is_vocal,
|
||||
'is_explicit' => $track->is_explicit,
|
||||
'is_downloadable' => $track->is_downloadable,
|
||||
'is_published' => $track->isPublished(),
|
||||
'published_at' => $track->published_at,
|
||||
'duration' => $track->duration,
|
||||
'genre' => [
|
||||
'id' => $track->genre->id,
|
||||
'slug' => $track->genre->slug,
|
||||
'name' => $track->genre->name
|
||||
],
|
||||
'track_type_id' => $track->track_type_id,
|
||||
'covers' => [
|
||||
'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL),
|
||||
'small' => $track->getCoverUrl(Image::SMALL),
|
||||
'normal' => $track->getCoverUrl(Image::NORMAL)
|
||||
]
|
||||
];
|
||||
$tracks[] = $this->mapPublicTrack($track);
|
||||
}
|
||||
|
||||
return Response::json($tracks, 200);
|
||||
}
|
||||
|
||||
public function getIndex() {
|
||||
$page = 1;
|
||||
|
||||
if (Input::has('page'))
|
||||
$page = Input::get('page');
|
||||
|
||||
$query = Track::summary()->whereNotNull('published_at');
|
||||
$this->applyFilters($query);
|
||||
|
||||
$totalCount = $query->count();
|
||||
$query->take(30)->skip(30 * ($page - 1));
|
||||
$tracks = [];
|
||||
|
||||
foreach ($query->get() as $track) {
|
||||
$tracks[] = $this->mapPublicTrack($track);
|
||||
}
|
||||
|
||||
return Response::json(["tracks" => $tracks, "current_page" => $page, "total_pages" => ceil($totalCount / 30)], 200);
|
||||
}
|
||||
|
||||
public function getOwned() {
|
||||
$query = Track::summary()->where('user_id', \Auth::user()->id);
|
||||
|
||||
|
@ -78,24 +71,7 @@
|
|||
$query->whereNull('published_at');
|
||||
}
|
||||
|
||||
if (Input::has('order')) {
|
||||
$order = \Input::get('order');
|
||||
$parts = explode(',', $order);
|
||||
$query->orderBy($parts[0], $parts[1]);
|
||||
}
|
||||
|
||||
if (Input::has('in_album')) {
|
||||
if (Input::get('in_album') == 'true')
|
||||
$query->whereNotNull('album_id');
|
||||
else
|
||||
$query->whereNull('album_id');
|
||||
}
|
||||
|
||||
if (Input::has('genres'))
|
||||
$query->whereIn('genre_id', Input::get('genres'));
|
||||
|
||||
if (Input::has('types'))
|
||||
$query->whereIn('track_type_id', Input::get('types'));
|
||||
$this->applyFilters($query);
|
||||
|
||||
$dbTracks = $query->get();
|
||||
$tracks = [];
|
||||
|
@ -159,4 +135,75 @@
|
|||
'album_id' => $track->album_id
|
||||
], 200);
|
||||
}
|
||||
|
||||
private function mapPublicTrack($track) {
|
||||
return [
|
||||
'id' => $track->id,
|
||||
'title' => $track->title,
|
||||
'user' => [
|
||||
'id' => $track->user->id,
|
||||
'name' => $track->user->display_name,
|
||||
'url' => $track->user->url
|
||||
],
|
||||
'url' => $track->url,
|
||||
'slug' => $track->slug,
|
||||
'is_vocal' => $track->is_vocal,
|
||||
'is_explicit' => $track->is_explicit,
|
||||
'is_downloadable' => $track->is_downloadable,
|
||||
'is_published' => $track->isPublished(),
|
||||
'published_at' => $track->published_at,
|
||||
'duration' => $track->duration,
|
||||
'genre' => $track->genre != null
|
||||
?
|
||||
[
|
||||
'id' => $track->genre->id,
|
||||
'slug' => $track->genre->slug,
|
||||
'name' => $track->genre->name
|
||||
] : null,
|
||||
'track_type_id' => $track->track_type_id,
|
||||
'covers' => [
|
||||
'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL),
|
||||
'small' => $track->getCoverUrl(Image::SMALL),
|
||||
'normal' => $track->getCoverUrl(Image::NORMAL)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function applyFilters($query) {
|
||||
if (Input::has('order')) {
|
||||
$order = \Input::get('order');
|
||||
$parts = explode(',', $order);
|
||||
$query->orderBy($parts[0], $parts[1]);
|
||||
}
|
||||
|
||||
if (Input::has('is_vocal')) {
|
||||
$isVocal = \Input::get('is_vocal');
|
||||
if ($isVocal == 'true')
|
||||
$query->whereIsVocal(true);
|
||||
else
|
||||
$query->whereIsVocal(false);
|
||||
}
|
||||
|
||||
if (Input::has('in_album')) {
|
||||
if (Input::get('in_album') == 'true')
|
||||
$query->whereNotNull('album_id');
|
||||
else
|
||||
$query->whereNull('album_id');
|
||||
}
|
||||
|
||||
if (Input::has('genres'))
|
||||
$query->whereIn('genre_id', Input::get('genres'));
|
||||
|
||||
if (Input::has('types'))
|
||||
$query->whereIn('track_type_id', Input::get('types'));
|
||||
|
||||
if (Input::has('songs')) {
|
||||
$query->join('show_song_track', 'tracks.id', '=', 'show_song_track.track_id')
|
||||
->whereIn('show_song_track.show_song_id', Input::get('songs'));
|
||||
|
||||
$query->select('tracks.*');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@
|
|||
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
||||
|
||||
Route::get('/tracks/recent', 'Api\Web\TracksController@getRecent');
|
||||
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
||||
|
||||
Route::get('/dashboard', 'Api\Web\DashboardController@getIndex');
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
@else
|
||||
<li ng-class="{selected: $state.includes('home')}"><a href="/">Home</a></li>
|
||||
@endif
|
||||
<li><a href="/tracks">Now Playing</a></li>
|
||||
<li><h3>Discover</h3></li>
|
||||
<li ng-class="{selected: $state.includes('tracks')}"><a href="/tracks">Music <i class="icon-music"></i></a></li>
|
||||
<li ng-class="{selected: $state.includes('albums')}"><a href="/albums">Albums <i class="icon-music"></i></a></li>
|
||||
|
|
BIN
public/images/pattern1.jpg
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
public/images/pattern10.jpg
Normal file
After Width: | Height: | Size: 140 KiB |
BIN
public/images/pattern2.jpg
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
public/images/pattern3.jpg
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
public/images/pattern4.jpg
Normal file
After Width: | Height: | Size: 218 KiB |
BIN
public/images/pattern5.jpg
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
public/images/pattern6.jpg
Normal file
After Width: | Height: | Size: 185 KiB |
BIN
public/images/pattern7.jpg
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
public/images/pattern8.jpg
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
public/images/pattern9.jpg
Normal file
After Width: | Height: | Size: 214 KiB |
BIN
public/images/sidebar-background.jpg
Normal file
After Width: | Height: | Size: 260 KiB |
BIN
public/images/test_pattern.jpg
Normal file
After Width: | Height: | Size: 88 KiB |
|
@ -69,7 +69,26 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
|
|||
|
||||
state.state 'tracks',
|
||||
url: '/tracks'
|
||||
templateUrl: '/templates/tracks/index.html'
|
||||
templateUrl: '/templates/tracks/_layout.html'
|
||||
abstract: true
|
||||
|
||||
state.state 'tracks.search',
|
||||
templateUrl: '/templates/tracks/search.html'
|
||||
controller: 'tracks'
|
||||
|
||||
state.state 'tracks.search.list',
|
||||
url: '?filter&page'
|
||||
templateUrl: '/templates/tracks/search-list.html'
|
||||
controller: 'tracks-list'
|
||||
|
||||
state.state 'tracks.popular',
|
||||
url: '/popular'
|
||||
templateUrl: '/templates/tracks/search.html'
|
||||
controller: 'tracks'
|
||||
|
||||
state.state 'tracks.random',
|
||||
url: '/random'
|
||||
templateUrl: '/templates/tracks/search.html'
|
||||
controller: 'tracks'
|
||||
|
||||
# Albums
|
||||
|
@ -121,7 +140,7 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
|
|||
if window.pfm.auth.isLogged
|
||||
state.state 'home',
|
||||
url: '/'
|
||||
templateUrl: '/templates/dashboard.html'
|
||||
templateUrl: '/templates/dashboard/index.html'
|
||||
controller: 'dashboard'
|
||||
else
|
||||
state.state 'home',
|
||||
|
|
|
@ -137,10 +137,6 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
|||
title: ''
|
||||
description: ''
|
||||
|
||||
window.onbeforeunload = ->
|
||||
return if !$scope.isDirty
|
||||
"Are you sure you want to leave this page without saving your changes?"
|
||||
|
||||
$scope.$on '$locationChangeStart', (e) ->
|
||||
return if !$scope.isDirty
|
||||
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?')
|
||||
|
|
|
@ -138,10 +138,6 @@ angular.module('ponyfm').controller "account-tracks-edit", [
|
|||
$scope.$emit 'track-deleted'
|
||||
$state.transitionTo 'account-content.tracks'
|
||||
|
||||
window.onbeforeunload = ->
|
||||
return if !$scope.isDirty
|
||||
"Are you sure you want to leave this page without saving your changes?"
|
||||
|
||||
$scope.$on '$locationChangeStart', (e) ->
|
||||
return if !$scope.isDirty
|
||||
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?')
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
window.pfm.preloaders['dashboard'] = [
|
||||
'dashboard'
|
||||
(dashboard) -> dashboard.refresh(true)
|
||||
]
|
||||
|
||||
angular.module('ponyfm').controller "dashboard", [
|
||||
'$scope'
|
||||
($scope) ->
|
||||
'$scope', 'dashboard'
|
||||
($scope, dashboard) ->
|
||||
$scope.recentTracks = null
|
||||
$scope.popularTracks = null
|
||||
|
||||
$scope.refresh = () ->
|
||||
$.getJSON('/api/web/dashboard')
|
||||
.done (res) -> $scope.$apply ->
|
||||
$scope.recentTracks = res.recent_tracks
|
||||
$scope.popularTracks = res.popular_tracks
|
||||
|
||||
$scope.refresh()
|
||||
dashboard.refresh().done (res) ->
|
||||
$scope.recentTracks = res.recent_tracks
|
||||
$scope.popularTracks = res.popular_tracks
|
||||
]
|
19
public/scripts/app/controllers/tracks-list.coffee
Normal file
|
@ -0,0 +1,19 @@
|
|||
window.pfm.preloaders['tracks-list'] = [
|
||||
'tracks', '$state'
|
||||
(tracks, $state) ->
|
||||
$.when.all [tracks.loadFilters().then(->
|
||||
if !tracks.mainQuery.hasLoadedFilters
|
||||
tracks.mainQuery.fromFilterString($state.params.filter)
|
||||
if $state.params.page
|
||||
tracks.mainQuery.setPage $state.params.page
|
||||
|
||||
tracks.mainQuery.fetch()
|
||||
)]
|
||||
]
|
||||
|
||||
angular.module('ponyfm').controller "tracks-list", [
|
||||
'$scope', 'tracks', '$state',
|
||||
($scope, tracks, $state) ->
|
||||
tracks.mainQuery.fetch().done (searchResults) ->
|
||||
$scope.tracks = searchResults.tracks
|
||||
]
|
|
@ -1,12 +1,33 @@
|
|||
angular.module('ponyfm').controller "tracks", [
|
||||
'$scope'
|
||||
($scope) ->
|
||||
'$scope', 'tracks', '$state'
|
||||
($scope, tracks, $state) ->
|
||||
$scope.recentTracks = null
|
||||
$scope.query = tracks.mainQuery
|
||||
$scope.filters = tracks.filters
|
||||
|
||||
$scope.refresh = () ->
|
||||
$.getJSON('/api/web/tracks/recent')
|
||||
.done (res) -> $scope.$apply ->
|
||||
$scope.recentTracks = res
|
||||
$scope.toggleListFilter = (filter, id) ->
|
||||
$scope.query.toggleListFilter filter, id
|
||||
$state.transitionTo 'tracks.search.list', {filter: $scope.query.toFilterString()}
|
||||
|
||||
$scope.refresh()
|
||||
$scope.setFilter = (filter, value) ->
|
||||
$scope.query.setFilter filter, value
|
||||
$state.transitionTo 'tracks.search.list', {filter: $scope.query.toFilterString()}
|
||||
|
||||
$scope.setListFilter = (filter, id) ->
|
||||
$scope.query.setListFilter filter, id
|
||||
$state.transitionTo 'tracks.search.list', {filter: $scope.query.toFilterString()}
|
||||
|
||||
tracks.mainQuery.listen (searchResults) ->
|
||||
$scope.tracks = searchResults.tracks
|
||||
$scope.currentPage = parseInt(searchResults.current_page)
|
||||
$scope.totalPages = parseInt(searchResults.total_pages)
|
||||
delete $scope.nextPage
|
||||
delete $scope.prevPage
|
||||
|
||||
$scope.nextPage = $scope.currentPage + 1 if $scope.currentPage < $scope.totalPages
|
||||
$scope.prevPage = $scope.currentPage - 1 if $scope.currentPage > 1
|
||||
$scope.pages = [1..$scope.totalPages]
|
||||
|
||||
$scope.gotoPage = (page) ->
|
||||
$state.transitionTo 'tracks.search.list', {filter: $state.params.filter, page: page}
|
||||
]
|
16
public/scripts/app/services/dashboard.coffee
Normal file
|
@ -0,0 +1,16 @@
|
|||
angular.module('ponyfm').factory('dashboard', [
|
||||
'$rootScope', '$http'
|
||||
($rootScope, $http) ->
|
||||
def = null
|
||||
|
||||
self =
|
||||
refresh: (force) ->
|
||||
force = force || false
|
||||
return def if !force && def
|
||||
def = new $.Deferred()
|
||||
$http.get('/api/web/dashboard').success (dashboardContent) ->
|
||||
def.resolve(dashboardContent)
|
||||
def.promise()
|
||||
|
||||
self
|
||||
])
|
|
@ -5,19 +5,31 @@ angular.module('ponyfm').factory('taxonomies', [
|
|||
|
||||
self =
|
||||
trackTypes: []
|
||||
trackTypesWithTracks: []
|
||||
licenses: []
|
||||
genres: []
|
||||
genresWithTracks: []
|
||||
showSongs: []
|
||||
showSongsWithTracks: []
|
||||
refresh: () ->
|
||||
return def.promise() if def != null
|
||||
|
||||
def = new $.Deferred()
|
||||
$http.get('/api/web/taxonomies/all')
|
||||
.success (taxonomies) ->
|
||||
self.trackTypes.push t for t in taxonomies.track_types
|
||||
for t in taxonomies.track_types
|
||||
self.trackTypes.push t
|
||||
self.trackTypesWithTracks.push t if t.track_count > 0
|
||||
|
||||
for t in taxonomies.genres
|
||||
self.genres.push t
|
||||
self.genresWithTracks.push t if t.track_count > 0
|
||||
|
||||
for t in taxonomies.show_songs
|
||||
self.showSongs.push t
|
||||
self.showSongsWithTracks.push t if t.track_count > 0
|
||||
|
||||
self.licenses.push t for t in taxonomies.licenses
|
||||
self.genres.push t for t in taxonomies.genres
|
||||
self.showSongs.push t for t in taxonomies.show_songs
|
||||
def.resolve self
|
||||
|
||||
def.promise()
|
||||
|
|
182
public/scripts/app/services/tracks.coffee
Normal file
|
@ -0,0 +1,182 @@
|
|||
angular.module('ponyfm').factory('tracks', [
|
||||
'$rootScope', '$http', 'taxonomies'
|
||||
($rootScope, $http, taxonomies) ->
|
||||
def = null
|
||||
|
||||
class Query
|
||||
cachedDef: null
|
||||
page: 1
|
||||
listeners: []
|
||||
|
||||
constructor: (@availableFilters) ->
|
||||
@filters = {}
|
||||
@hasLoadedFilters = false
|
||||
|
||||
_.each @availableFilters, (filter, name) =>
|
||||
if filter.type == 'single'
|
||||
@filters[name] = _.find filter.values, (f) -> f.isDefault
|
||||
else
|
||||
@filters[name] = {title: 'Any', selectedArray: [], selectedObject: {}}
|
||||
|
||||
isIdSelected: (type, id) ->
|
||||
@filters[type].selectedObject[id] != undefined
|
||||
|
||||
listen: (listener) ->
|
||||
@listeners.push listener
|
||||
@cachedDef.done listener if @cachedDef
|
||||
|
||||
setListFilter: (type, id) ->
|
||||
@cachedDef = null
|
||||
@page = 1
|
||||
filterToAdd = _.find @availableFilters[type].values, (f) -> `f.id == id`
|
||||
return if !filterToAdd
|
||||
|
||||
filter = @filters[type]
|
||||
filter.selectedArray = [filterToAdd]
|
||||
filter.selectedObject = {}
|
||||
filter.selectedObject[id] = filterToAdd
|
||||
filter.title = filterToAdd.title
|
||||
|
||||
toggleListFilter: (type, id) ->
|
||||
@cachedDef = null
|
||||
@page = 1
|
||||
filter = @filters[type]
|
||||
|
||||
if filter.selectedObject[id]
|
||||
delete filter.selectedObject[id]
|
||||
filter.selectedArray.splice _.indexOf(filter.selectedArray, (f) -> f.id == id), 1
|
||||
else
|
||||
filterToAdd = _.find @availableFilters[type].values, (f) -> `f.id == id`
|
||||
return if !filterToAdd
|
||||
filter.selectedObject[id] = filterToAdd
|
||||
filter.selectedArray.push filterToAdd
|
||||
|
||||
if filter.selectedArray.length == 0
|
||||
filter.title = 'Any'
|
||||
else if filter.selectedArray.length == 1
|
||||
filter.title = filter.selectedArray[0].title
|
||||
else
|
||||
filter.title = filter.selectedArray.length + ' selected'
|
||||
|
||||
setPage: (page) ->
|
||||
@page = page
|
||||
@cachedDef = null
|
||||
|
||||
setFilter: (type, value) ->
|
||||
@cachedDef = null
|
||||
@page = 1
|
||||
@filters[type] = value
|
||||
|
||||
toFilterString: ->
|
||||
parts = []
|
||||
_.each @availableFilters, (filter, name) =>
|
||||
if filter.type == 'single'
|
||||
return if @filters[name].query == ''
|
||||
parts.push(name + '-' + @filters[name].query)
|
||||
else
|
||||
return if @filters[name].selectedArray.length == 0
|
||||
parts.push(name + '-' + _.map(@filters[name].selectedArray, (f) -> f.id).join '-')
|
||||
|
||||
return parts.join '!'
|
||||
|
||||
fromFilterString: (str) ->
|
||||
@hasLoadedFilters = true
|
||||
return if !str
|
||||
filters = str.split '!'
|
||||
for filter in filters
|
||||
parts = filter.split '-'
|
||||
name = parts[0]
|
||||
return if !@availableFilters[name]
|
||||
|
||||
if @availableFilters[name].type == 'single'
|
||||
filterToSet = _.find @availableFilters[name].values, (f) -> f.query == parts[1]
|
||||
filterToSet = _.find @availableFilters[name].values, (f) -> f.isDefault if filterToSet == null
|
||||
else
|
||||
@toggleListFilter name, id for id in _.rest parts, 1
|
||||
|
||||
fetch: () ->
|
||||
return @cachedDef if @cachedDef
|
||||
@cachedDef = new $.Deferred()
|
||||
def = @cachedDef
|
||||
|
||||
query = '/api/web/tracks?'
|
||||
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 (tracks) =>
|
||||
@tracks = tracks
|
||||
for listener in @listeners
|
||||
listener tracks
|
||||
|
||||
def.resolve tracks
|
||||
|
||||
def.promise()
|
||||
|
||||
self =
|
||||
filters: {}
|
||||
|
||||
createQuery: -> new Query self.filters
|
||||
loadFilters: ->
|
||||
return def if def
|
||||
|
||||
def = new $.Deferred()
|
||||
self.filters.isVocal =
|
||||
type: 'single'
|
||||
values: [
|
||||
{title: 'Either', query: '', isDefault: true, filter: ''},
|
||||
{title: 'Yes', query: 'yes', isDefault: false, filter: 'is_vocal=true'},
|
||||
{title: 'No', query: 'no', isDefault: false, filter: 'is_vocal=false'}
|
||||
]
|
||||
|
||||
self.filters.sort =
|
||||
type: 'single'
|
||||
values: [
|
||||
{title: 'Newest to Oldest', query: '', isDefault: true, filter: 'order=created_at,desc'},
|
||||
{title: 'Oldest to Newest', query: 'created_at,asc', isDefault: true, filter: 'order=created_at,asc'}
|
||||
]
|
||||
|
||||
self.filters.genres =
|
||||
type: 'list'
|
||||
values: []
|
||||
filterName: 'genres'
|
||||
|
||||
self.filters.trackTypes =
|
||||
type: 'list'
|
||||
values: []
|
||||
filterName: 'types'
|
||||
|
||||
self.filters.showSongs =
|
||||
type: 'list'
|
||||
values: []
|
||||
filterName: 'songs'
|
||||
|
||||
taxonomies.refresh().done (taxes) ->
|
||||
for genre in taxes.genresWithTracks
|
||||
self.filters.genres.values.push
|
||||
title: genre.name
|
||||
id: genre.id
|
||||
|
||||
for type in taxes.trackTypesWithTracks
|
||||
self.filters.trackTypes.values.push
|
||||
title: type.title
|
||||
id: type.id
|
||||
|
||||
for song in taxes.showSongsWithTracks
|
||||
self.filters.showSongs.values.push
|
||||
title: song.title
|
||||
id: song.id
|
||||
|
||||
self.mainQuery = self.createQuery()
|
||||
def.resolve self
|
||||
|
||||
def.promise()
|
||||
|
||||
self
|
||||
])
|
|
@ -1,5 +1,5 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'mixins';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
html {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'mixins';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
.slide-down-enter, .slide-down-leave,
|
||||
.slide-up-enter, .slide-up-leave,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'base/font-awesome/font-awesome';
|
||||
@import 'mixins';
|
||||
@import 'layout';
|
||||
@import 'home';
|
||||
@import 'account-content';
|
||||
@import 'components';
|
||||
@import 'forms';
|
||||
@import 'tracks';
|
||||
@import 'animations';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'base/font-awesome/font-awesome';
|
||||
@import-once 'mixins';
|
||||
@import-once 'layout';
|
||||
@import-once 'home';
|
||||
@import-once 'account-content';
|
||||
@import-once 'components';
|
||||
@import-once 'forms';
|
||||
@import-once 'tracks';
|
||||
@import-once 'animations';
|
|
@ -1,5 +1,5 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'mixins';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
.fade-hide, .fade-show {
|
||||
.transition(all cubic-bezier(0.250, 0.460, 0.450, 0.940) 350ms);
|
||||
|
@ -51,6 +51,25 @@ html .dropdown-menu {
|
|||
padding: 3px 10px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
> .dont-close {
|
||||
float: right;
|
||||
display: block;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
padding: 3px 3px;
|
||||
}
|
||||
|
||||
> .dont-close + a {
|
||||
margin-right: 30px;
|
||||
clear: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
> .dont-close i:before {
|
||||
content: "\f068";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,12 +98,11 @@ html .dropdown-menu {
|
|||
|
||||
li.selected {
|
||||
a {
|
||||
#gradient>.vertical(@green, darken(@green, 5%));
|
||||
background: @green;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
#gradient>.vertical(fadeout(@green, 20%), fadeout(darken(@green, 5%), 20%));
|
||||
background: fadeout(@green, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,3 +189,24 @@ html .dropdown-menu {
|
|||
.border-radius(0px);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
.pagination {
|
||||
border: none;
|
||||
|
||||
&.pagination-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
li a {
|
||||
.border-radius(0px);
|
||||
border: none;
|
||||
padding: 1px 10px;
|
||||
}
|
||||
|
||||
li.active a {
|
||||
background: #444;
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'mixins';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
|
@ -13,6 +13,7 @@ html body {
|
|||
|
||||
.background-color {
|
||||
background: rgba(42, 42, 42, 1);
|
||||
background-image: url('/images/pattern4.jpg');
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
|
@ -32,7 +33,7 @@ ui-view {
|
|||
content: ' ';
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1000;
|
||||
z-index: 999;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
|
@ -53,6 +54,7 @@ ui-view {
|
|||
|
||||
header {
|
||||
.clearfix();
|
||||
.box-shadow(0px 1px 8px rgba(0, 0, 0, 0.2));
|
||||
background: #222;
|
||||
|
||||
> div {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import 'base/bootstrap/bootstrap';
|
||||
@import 'mixins';
|
||||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
.dashboard {
|
||||
section {
|
||||
|
@ -24,25 +24,72 @@
|
|||
padding: 0px;
|
||||
list-style: none;
|
||||
|
||||
&.two-column {
|
||||
li {
|
||||
.box-sizing(border-box);
|
||||
|
||||
width: 50%;
|
||||
float: left;
|
||||
margin: 0px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
overflow: hidden;
|
||||
margin: 5px 0px;
|
||||
margin: 10px 0px;
|
||||
padding: 0px;
|
||||
padding-bottom: 5px;
|
||||
|
||||
img {
|
||||
.img-polaroid();
|
||||
padding: 3px;
|
||||
padding: 0px;
|
||||
display: block;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
|
||||
.image {
|
||||
.play-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
float: left;
|
||||
position: relative;
|
||||
|
||||
.play-button {
|
||||
.transition(background 250ms ease-out);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background: rgba(0, 0, 0, .4);
|
||||
display: none;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .8);
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
.img-polaroid();
|
||||
padding: 3px;
|
||||
padding: 0px;
|
||||
display: block;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
float: right;
|
||||
font-size: 13px;
|
||||
margin-right: 2px;
|
||||
|
||||
a, span {
|
||||
display: block;
|
||||
|
@ -56,7 +103,11 @@
|
|||
}
|
||||
|
||||
.info {
|
||||
margin-left: 51px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 3px 0px;
|
||||
margin-left: 40px;
|
||||
padding-left: 5px;
|
||||
line-height: normal;
|
||||
|
||||
.title {
|
||||
|
@ -71,10 +122,6 @@
|
|||
display: block;
|
||||
color: #777;
|
||||
font-size: 8pt;
|
||||
|
||||
a {
|
||||
color: fadeOut(@blue, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,23 @@
|
|||
</li>
|
||||
</ul>
|
||||
<div class="stretch-to-bottom">
|
||||
<div class="form-row" ng-class="{'has-error': errors.display_name != null}">
|
||||
<label for="sync_names" class="strong"><input ng-disabled="isSaving" ng-change="touchModel();" id="sync_names" type="checkbox" ng-model="settings.sync_names" /> Sync my MLP Forums display name with Pony.fm</label>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="!settings.sync_names" placeholder="Display Name" id="display_name" ng-model="settings.display_name" />
|
||||
<div ng-show="settings.sync_names" class="alert alert-info">Your current MLP Forums display name is <strong>{{settings.mlpforums_name}}</strong></div>
|
||||
<div class="error">{{errors.display_name}}</div>
|
||||
</div>
|
||||
<div class="form-row" ng-class="{'has-error': errors.bio != null}">
|
||||
<label class="strong" for="bio">Bio</label>
|
||||
<textarea id="bio" placeholder="bio (optional)" ng-model="settings.bio" ng-disabled="isLoading" ng-change="touchModel()"></textarea>
|
||||
<div class="error">{{errors.description}}</div>
|
||||
<div class="row-fluid">
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.display_name != null}">
|
||||
<label for="sync_names" class="strong"><input ng-disabled="isSaving" ng-change="touchModel();" id="sync_names" type="checkbox" ng-model="settings.sync_names" /> Sync my MLP Forums display name with Pony.fm</label>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="!settings.sync_names" placeholder="Display Name" id="display_name" ng-model="settings.display_name" />
|
||||
<div ng-show="settings.sync_names" class="alert alert-info">Your current MLP Forums display name is <strong>{{settings.mlpforums_name}}</strong></div>
|
||||
<div class="error">{{errors.display_name}}</div>
|
||||
</div>
|
||||
<div class="form-row span6">
|
||||
<label for="can_see_explicit_content"><input ng-change="touchModel()" ng-disabled="isLoading" id="can_see_explicit_content" type="checkbox" ng-model="settings.can_see_explicit_content" /> Can See Explicit Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.bio != null}">
|
||||
<label class="strong" for="bio">Bio</label>
|
||||
<textarea id="bio" placeholder="bio (optional)" ng-model="settings.bio" ng-disabled="isLoading" ng-change="touchModel()"></textarea>
|
||||
<div class="error">{{errors.description}}</div>
|
||||
</div>
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.avatar != null || errors.gravatar != null}">
|
||||
<label for="uses_gravatar">
|
||||
<input ng-change="touchModel()" ng-disabled="isLoading" id="uses_gravatar" type="checkbox" ng-model="settings.uses_gravatar" /> Use Gravatar
|
||||
|
@ -33,9 +38,6 @@
|
|||
<div class="error" ng-show="errors.avatar != null">{{errors.avatar}}</div>
|
||||
<div class="error" ng-show="errors.gravatar != null">{{errors.gravatar}}</div>
|
||||
</div>
|
||||
<div class="form-row span6">
|
||||
<label for="can_see_explicit_content"><input ng-change="touchModel()" ng-disabled="isLoading" id="can_see_explicit_content" type="checkbox" ng-model="settings.can_see_explicit_content" /> Can See Explicit Content</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<div class="dashboard">
|
||||
<section class="recent-tracks">
|
||||
<div>
|
||||
<h1>
|
||||
<a href="#"><i class="icon-music"></i> see more</a>
|
||||
The Newest Tunes
|
||||
</h1>
|
||||
<ul class="tracks-listing stretch-to-bottom">
|
||||
<li ng-repeat="track in recentTracks">
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
<div class="icons">
|
||||
<span><i ng-class="{'icon-microphone-off': !track.is_vocal, 'icon-microphone': track.is_vocal}"></i></span>
|
||||
<a href="#"><i class="icon-star-empty"></i></a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{{track.url}}" class="title">{{track.title}}</a>
|
||||
<span class="metadata">
|
||||
by: <a href="{{track.user.url}}">{{track.user.name}}</a> /
|
||||
<a href="#">{{track.genre.name}}</a> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section class="popular">
|
||||
<div>
|
||||
<h1>
|
||||
<a href="#"><i class="icon-star"></i> see more</a>
|
||||
What's Popular Today
|
||||
</h1>
|
||||
<ul class="tracks-listing stretch-to-bottom">
|
||||
<li ng-repeat="track in recentTracks">
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
<div class="icons">
|
||||
<span><i ng-class="{'icon-microphone-off': !track.is_vocal, 'icon-microphone': track.is_vocal}"></i></span>
|
||||
<a href="#"><i class="icon-star-empty"></i></a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{{track.url}}" class="title">{{track.title}}</a>
|
||||
<span class="metadata">
|
||||
by: <a href="{{track.user.url}}">{{track.user.name}}</a> /
|
||||
<a href="#">{{track.genre.name}}</a> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
|
@ -7,19 +7,22 @@
|
|||
</h1>
|
||||
<ul class="tracks-listing stretch-to-bottom">
|
||||
<li ng-repeat="track in recentTracks">
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
<div class="image">
|
||||
<a href="#" class="play-button"><i class="icon-play"></i></a>
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
</div>
|
||||
<div class="icons">
|
||||
<span><i ng-class="{'icon-microphone-off': !track.is_vocal, 'icon-microphone': track.is_vocal}"></i></span>
|
||||
<a href="#"><i class="icon-star-empty"></i></a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{{track.url}}" class="title">{{track.title}}</a>
|
||||
<span class="metadata">
|
||||
by: <a href="{{track.user.url}}">{{track.user.name}}</a> /
|
||||
<a href="#">{{track.genre.name}}</a> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</div>
|
||||
<a class="info" href="{{track.url}}">
|
||||
<span class="title">{{track.title}}</span>
|
||||
<span class="metadata">
|
||||
by: <span class="artist">{{track.user.name}}</span> /
|
||||
<span class="genre">{{track.genre.name}}</span> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -32,19 +35,22 @@
|
|||
</h1>
|
||||
<ul class="tracks-listing stretch-to-bottom">
|
||||
<li ng-repeat="track in recentTracks">
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
<div class="image">
|
||||
<a href="#" class="play-button"><i class="icon-play"></i></a>
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
</div>
|
||||
<div class="icons">
|
||||
<span><i ng-class="{'icon-microphone-off': !track.is_vocal, 'icon-microphone': track.is_vocal}"></i></span>
|
||||
<a href="#"><i class="icon-star-empty"></i></a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{{track.url}}" class="title">{{track.title}}</a>
|
||||
<span class="metadata">
|
||||
by: <a href="{{track.user.url}}">{{track.user.name}}</a> /
|
||||
<a href="#">{{track.genre.name}}</a> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</div>
|
||||
<a class="info" href="{{track.url}}">
|
||||
<span class="title">{{track.title}}</span>
|
||||
<span class="metadata">
|
||||
by: <span class="artist">{{track.user.name}}</span> /
|
||||
<span class="genre">{{track.genre.name}}</span> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
9
public/templates/tracks/_layout.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div>
|
||||
<ul class="tabs">
|
||||
<li ng-class="{active: $state.includes('tracks.search')}"><a href="/tracks">Search</a></li>
|
||||
<li ng-class="{active: $state.includes('tracks.popular')}"><a href="/tracks/popular">Popular</a></li>
|
||||
<li ng-class="{active: $state.includes('tracks.random')}"><a href="/tracks/random">Random</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
||||
</div>
|
20
public/templates/tracks/search-list.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<ul class="tracks-listing two-column stretch-to-bottom">
|
||||
<li ng-repeat="track in tracks">
|
||||
<div class="image">
|
||||
<a href="#" class="play-button"><i class="icon-play"></i></a>
|
||||
<img ng-src="{{track.covers.thumbnail}}" />
|
||||
</div>
|
||||
<div class="icons">
|
||||
<span><i ng-class="{'icon-microphone-off': !track.is_vocal, 'icon-microphone': track.is_vocal}"></i></span>
|
||||
<a href="#"><i class="icon-star-empty"></i></a>
|
||||
</div>
|
||||
<a class="info" href="{{track.url}}">
|
||||
<span class="title">{{track.title}}</span>
|
||||
<span class="metadata">
|
||||
by: <span class="artist">{{track.user.name}}</span> /
|
||||
<span class="genre">{{track.genre.name}}</span> /
|
||||
{{track.published_at.date | momentFromNow}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
67
public/templates/tracks/search.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<ul class="dropdowns">
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Type: <strong>{{query.filters.trackTypes.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="type in filters.trackTypes.values" ng-class="{selected: query.isIdSelected('trackTypes', type.id)}">
|
||||
<a class="dont-close" pfm-eat-click href="#" ng-click="toggleListFilter('trackTypes', type.id); $event.stopPropagation();"><i class="icon-plus"></i></a>
|
||||
<a pfm-eat-click href="#" ng-click="setListFilter('trackTypes', type.id);">{{type.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Show Songs: <strong>{{query.filters.showSongs.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="song in filters.showSongs.values" ng-class="{selected: query.isIdSelected('showSongs', song.id)}">
|
||||
<a class="dont-close" pfm-eat-click href="#" ng-click="toggleListFilter('showSongs', song.id); $event.stopPropagation();"><i class="icon-plus"></i></a>
|
||||
<a pfm-eat-click href="#" ng-click="setListFilter('showSongs', song.id);">{{song.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Genre: <strong>{{query.filters.genres.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="genre in filters.genres.values" ng-class="{selected: query.isIdSelected('genres', genre.id)}">
|
||||
<a class="dont-close" pfm-eat-click href="#" ng-click="toggleListFilter('genres', genre.id); $event.stopPropagation();"><i class="icon-plus"></i></a>
|
||||
<a pfm-eat-click href="#" ng-click="setListFilter('genres', genre.id);">{{genre.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Is Vocal: <strong>{{query.filters.isVocal.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="item in filters.isVocal.values" ng-class="{selected: item == query.filters.isVocal}">
|
||||
<a pfm-eat-click href="#" ng-click="setFilter('isVocal', item);">{{item.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Order: <strong>{{query.filters.sort.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="filter in filters.sort.values" 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-show="totalPages > 0">
|
||||
<ul>
|
||||
<li ng-class="{disabled: !prevPage}"><a href="#" ng-click="gotoPage(prevPage);" pfm-eat-click>Prev</a></li>
|
||||
<li ng-repeat="page in pages" ng-class="{active: page == currentPage}">
|
||||
<a href="#" ng-click="gotoPage(page);" pfm-eat-click>{{page}}</a>
|
||||
</li>
|
||||
<li ng-class="{disabled: !nextPage}"><a href="#" ng-click="gotoPage(nextPage);" pfm-eat-click>Next</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ui-view></ui-view>
|