Track searching stuff

This commit is contained in:
nelsonlaquet 2013-07-31 06:47:16 -05:00
parent d4789ebda3
commit 8d3d126550
37 changed files with 648 additions and 199 deletions

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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');

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
public/images/pattern10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
public/images/pattern2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
public/images/pattern3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
public/images/pattern4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
public/images/pattern5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
public/images/pattern6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
public/images/pattern7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
public/images/pattern8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
public/images/pattern9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -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',

View file

@ -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?')

View file

@ -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?')

View file

@ -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
]

View 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
]

View file

@ -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}
]

View 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
])

View file

@ -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()

View 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
])

View file

@ -1,5 +1,5 @@
@import 'base/bootstrap/bootstrap';
@import 'mixins';
@import-once 'base/bootstrap/bootstrap';
@import-once 'mixins';
@media (max-width: 1200px) {
html {

View file

@ -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,

View file

@ -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';

View file

@ -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;
}
}
}

View file

@ -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 {

View file

@ -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%);
}
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View 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>