Volume Slider
Track Uploader
Fav list
A bunch of other things
And stuff.
This commit is contained in:
nelsonlaquet 2013-08-30 20:46:35 -05:00
parent 1dff7cb36f
commit 18e41d2af4
58 changed files with 1013 additions and 359 deletions

View file

@ -3,10 +3,96 @@
namespace Api\Web;
use Commands\ToggleFavouriteCommand;
use Entities\Album;
use Entities\Favourite;
use Entities\Playlist;
use Entities\Track;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
class FavouritesController extends \ApiControllerBase {
public function postToggle() {
return $this->execute(new ToggleFavouriteCommand(Input::get('type'), Input::get('id')));
}
public function getTracks() {
$query = Favourite
::whereUserId(Auth::user()->id)
->whereNotNull('track_id')
->with([
'track' => function($query) {
$query
->userDetails()
->published();
},
'track.user',
'track.genre',
'track.cover',
'track.album',
'track.album.user'
]);
$tracks = [];
foreach ($query->get() as $fav) {
if ($fav->track == null) // deleted track
continue;
$tracks[] = Track::mapPublicTrackSummary($fav->track);
}
return Response::json(["tracks" => $tracks], 200);
}
public function getAlbums() {
$query = Favourite
::whereUserId(Auth::user()->id)
->whereNotNull('album_id')
->with([
'album' => function($query) {
$query->userDetails();
},
'album.user',
'album.user.avatar',
'album.cover'
]);
$albums = [];
foreach ($query->get() as $fav) {
if ($fav->album == null) // deleted album
continue;
$albums[] = Album::mapPublicAlbumSummary($fav->album);
}
return Response::json(["albums" => $albums], 200);
}
public function getPlaylists() {
$query = Favourite
::whereUserId(Auth::user()->id)
->whereNotNull('playlist_id')
->with([
'playlist' => function($query) {
$query->userDetails();
},
'playlist.user',
'playlist.user.avatar',
'playlist.tracks',
'playlist.tracks.cover'
]);
$playlists = [];
foreach ($query->get() as $fav) {
if ($fav->playlist == null) // deleted playlist
continue;
$playlists[] = Playlist::mapPublicPlaylistSummary($fav->playlist);
}
return Response::json(["playlists" => $playlists], 200);
}
}

View file

@ -0,0 +1,7 @@
<?php
class UploaderController extends Controller {
public function getIndex() {
return View::make('shared.null');
}
}

View file

@ -35,12 +35,11 @@
* @return CommandResponse
*/
public function execute() {
$isVocal = isset($this->_input['is_vocal']) && $this->_input['is_vocal'] == 'true' ? true : false;
$isVocal = (isset($this->_input['is_vocal']) && $this->_input['is_vocal'] == 'true') ? true : false;
$rules = [
'title' => 'required|min:3|max:80',
'released_at' => 'before:today' . ($this->_input['released_at'] != "" ? '|date' : ''),
'lyrics' => $isVocal ? 'required' : '',
'license_id' => 'required|exists:licenses,id',
'genre_id' => 'required|exists:genres,id',
'cover' => 'image|mimes:png|min_width:350|min_height:350',
@ -50,6 +49,9 @@
'album_id' => 'exists:albums,id'
];
if ($isVocal)
$rules['lyrics'] = 'required';
if ($this->_input['track_type_id'] == 2)
$rules['show_song_ids'] = 'required|exists:show_songs,id';
@ -80,7 +82,7 @@
$track->album_id = $this->_input['album_id'];
Album::whereId($album->id)->update([
'track_count' => DB::raw('SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id)
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id . ')')
]);
}
} else {

View file

@ -132,7 +132,11 @@
'name' => $album->user->display_name,
'url' => $album->user->url,
],
'user_data' => $userData
'user_data' => $userData,
'permissions' => [
'delete' => Auth::check() && Auth::user()->id == $album->user_id,
'edit' => Auth::check() && Auth::user()->id == $album->user_id
]
];
}

View file

@ -105,7 +105,11 @@
'name' => $playlist->user->display_name,
'url' => $playlist->user->url,
],
'user_data' => $userData
'user_data' => $userData,
'permissions' => [
'delete' => Auth::check() && Auth::user()->id == $playlist->user_id,
'edit' => Auth::check() && Auth::user()->id == $playlist->user_id
]
];
}

View file

@ -38,8 +38,6 @@
$query->whereUserId(Auth::user()->id);
}]);
}
return !$query;
}
public function scopePublished($query) {
@ -194,7 +192,11 @@
'streams' => [
'mp3' => $track->getStreamUrl('MP3')
],
'user_data' => $userData
'user_data' => $userData,
'permissions' => [
'delete' => Auth::check() && Auth::user()->id == $track->user_id,
'edit' => Auth::check() && Auth::user()->id == $track->user_id
]
];
}

View file

@ -106,6 +106,10 @@
Route::get('/playlists/owned', 'Api\Web\PlaylistsController@getOwned');
Route::get('/playlists/pinned', 'Api\Web\PlaylistsController@getPinned');
Route::get('/favourites/tracks', 'Api\Web\FavouritesController@getTracks');
Route::get('/favourites/albums', 'Api\Web\FavouritesController@getAlbums');
Route::get('/favourites/playlists', 'Api\Web\FavouritesController@getPlaylists');
});
Route::group(['before' => 'csrf'], function(){
@ -127,6 +131,8 @@
Route::get('/albums/create', 'ContentController@getAlbums');
Route::get('/playlists', 'ContentController@getPlaylists');
Route::get('/uploader', 'UploaderController@getIndex');
Route::get('/', 'AccountController@getIndex');
});
});

View file

@ -26,6 +26,18 @@
<header>
<a href="/">Pony.fm</a>
<div class="now-playing">
@if (Auth::check())
<div class="user-details dropdown">
<a class="avatar dropdown-toggle" href="#">
<img src="{{Auth::user()->getAvatarUrl(\Entities\Image::THUMBNAIL)}}" />
<span><i class="icon-chevron-down"></i></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{Auth::user()->url}}">Your Profile</a></li>
<li><a href="#" pfm-eat-click ng-click="logout()">Logout</a></li>
</ul>
</div>
@endif
<pfm-player></pfm-player>
</div>
</header>
@ -42,29 +54,25 @@
</li>
@if (Auth::check())
<li ng-class="{selected: stateIncludes('account-content') || isActive('/account')}"><a href="/account/tracks">Account <i class="icon-user"></i></a></li>
<li ng-class="{selected: stateIncludes('account')}"><a href="/account/tracks">Account <i class="icon-user"></i></a></li>
<li ng-class="{selected: stateIncludes('favourites')}"><a href="/account/favourites/tracks">Favourites <i class="icon-star"></i></a></li>
@endif
<li ng-class="{selected: isActive('/about')}"><a href="/about">Meta <i class="icon-info"></i></a></li>
<li ng-class="{selected: isActive('/about')}"><a href="/about">About <i class="icon-info"></i></a></li>
@if (Auth::check())
<li class="uploader" ng-class="{selected: stateIncludes('uploader')}">
<a href="/account/uploader">Upload Music <i class="icon-upload-alt"></i></a>
</li>
<li>
<h3>
<a href="#" ng-click="createPlaylist()" pfm-eat-click title="Create Playlist"><i class="icon-plus"></i></a>
<a href="/account/playlists" ng-class="{selected: $state.is('account-content-playlists')}" title="View Playlists" class="view-all"><i class="icon-list"></i></a>
Playlists
</h3>
</li>
<li class="none" ng-show="!playlists.length"><span>no pinned playlists</span></li>
<li class="dropdown" ng-repeat="playlist in playlists" ng-cloak ng-class="{selected: stateIncludes('content.playlist') && $state.params.id == playlist.id}">
<a class="menu dropdown-toggle" pfm-eat-click href="#"><i class="icon-ellipsis-vertical"></i></a>
<a href="{{Helpers::angular('playlist.url')}}" ng-bind="playlist.title"></a>
<ul class="dropdown-menu">
<li><a href="#" pfm-eat-click ng-click="editPlaylist(playlist)">Edit</a></li>
<li><a href="#" pfm-eat-click ng-click="unpinPlaylist(playlist)">Unpin</a></li>
<li><a href="#" pfm-eat-click ng-click="deletePlaylist(playlist)" ng-show="playlist.user_id == auth.user_id">Delete</a></li>
</ul>
</li>
@endif
</ul>
@ -73,8 +81,6 @@
</ui-view>
</div>
<ng-include src="'templates/partials/upload-dialog.html'"></ng-include>
@endsection
@section('styles')

View file

@ -10,7 +10,7 @@
@yield('styles')
</head>
<body ng-app="ponyfm" ng-controller="application" uploader>
<body ng-app="ponyfm" ng-controller="application">
@yield('content')
@yield('scripts')
</body>

View file

@ -6,64 +6,74 @@ module.config [
'$locationProvider', '$stateProvider', '$dialogProvider'
(location, state, $dialogProvider) ->
# Upload
state.state 'uploader',
url: '/account/uploader'
templateUrl: '/templates/uploader/index.html'
controller: 'uploader'
# Account
state.state 'account',
url: '/account'
abstract: true
templateUrl: '/templates/account/_layout.html'
state.state 'account.settings',
url: ''
templateUrl: '/templates/account/settings.html'
controller: 'account-settings'
state.state 'account-content',
url: '/account'
abstract: true
templateUrl: '/templates/account/content/_layout.html'
state.state 'account-content.tracks',
state.state 'account.tracks',
url: '/tracks'
templateUrl: '/templates/account/content/tracks.html'
templateUrl: '/templates/account/tracks.html'
controller: 'account-tracks'
state.state 'account-content.tracks.edit',
state.state 'account.tracks.edit',
url: '/edit/:track_id'
templateUrl: '/templates/account/content/track.html'
controller: 'account-tracks-edit'
templateUrl: '/templates/account/track.html'
controller: 'account-track'
state.state 'account-content.albums',
state.state 'account.albums',
url: '/albums'
templateUrl: '/templates/account/content/albums.html'
templateUrl: '/templates/account/albums.html'
controller: 'account-albums'
state.state 'account-content.albums.create',
state.state 'account.albums.create',
url: '/create'
templateUrl: '/templates/account/content/album.html'
templateUrl: '/templates/account/album.html'
controller: 'account-albums-edit'
state.state 'account-content.albums.edit',
state.state 'account.albums.edit',
url: '/edit/:album_id'
templateUrl: '/templates/account/content/album.html'
templateUrl: '/templates/account/album.html'
controller: 'account-albums-edit'
state.state 'account-content-playlists',
url: '/account/playlists'
templateUrl: '/templates/account/content/playlists.html'
state.state 'account.playlists',
url: '/playlists'
templateUrl: '/templates/account/playlists.html'
controller: 'account-playlists'
state.state 'account-favourites',
state.state 'favourites',
url: '/account/favourites'
abstract: true
templateUrl: '/templates/account/favourites/_layout.html'
templateUrl: '/templates/favourites/_layout.html'
state.state 'account-favourites.tracks',
url: ''
templateUrl: '/templates/account/favourites/tracks.html'
state.state 'favourites.tracks',
url: '/tracks'
templateUrl: '/templates/favourites/tracks.html'
controller: 'favourites-tracks'
state.state 'account-favourites.playlists',
state.state 'favourites.playlists',
url: '/playlists'
templateUrl: '/templates/account/favourites/playlists.html'
templateUrl: '/templates/favourites/playlists.html'
controller: 'favourites-playlists'
state.state 'account-favourites.albums',
state.state 'favourites.albums',
url: '/albums'
templateUrl: '/templates/account/favourites/albums.html'
templateUrl: '/templates/favourites/albums.html'
controller: 'favourites-albums'
# Tracks
@ -72,14 +82,14 @@ module.config [
templateUrl: '/templates/content/_layout.html'
state.state 'content.tracks',
templateUrl: '/templates/tracks/search.html'
templateUrl: '/templates/tracks/index.html'
controller: 'tracks'
url: '/tracks'
abstract: true
state.state 'content.tracks.list',
url: '^/tracks?filter&page'
templateUrl: '/templates/tracks/search-list.html'
templateUrl: '/templates/tracks/list.html'
controller: 'tracks-list'
state.state 'content.track',

View file

@ -73,7 +73,7 @@ angular.module('ponyfm').controller "account-albums-edit", [
if $scope.isNew
$scope.isDirty = false
$scope.$emit 'album-created'
$state.transitionTo 'account-content.albums.edit', {album_id: response.id}
$state.transitionTo 'account.albums.edit', {album_id: response.id}
else
$scope.isDirty = false
$scope.data.selectedAlbum.title = $scope.album.title
@ -104,7 +104,7 @@ angular.module('ponyfm').controller "account-albums-edit", [
$.post('/api/web/albums/delete/' + $scope.album.id, {_token: window.pfm.token})
.then -> $scope.$apply ->
$scope.$emit 'album-deleted'
$state.transitionTo 'account-content.albums'
$state.transitionTo 'account.albums'
$scope.setCover = (image, type) ->
delete $scope.album.cover_id

View file

@ -13,6 +13,8 @@ angular.module('ponyfm').controller "account-albums", [
selectedAlbum: null
tracksDb: []
selectAlbum = (album) -> $scope.data.selectedAlbum = album
updateTracks = (tracks) ->
$scope.data.tracksDb.push track for track in tracks
@ -32,8 +34,6 @@ angular.module('ponyfm').controller "account-albums", [
albums.refresh().done updateAlbums
selectAlbum = (album) -> $scope.data.selectedAlbum = album
$scope.$on '$stateChangeSuccess', () ->
if $state.params.album_id
selectAlbum albumsDb[$state.params.album_id]

View file

@ -1,12 +1,12 @@
window.pfm.preloaders['account-tracks-edit'] = [
window.pfm.preloaders['account-track'] = [
'account-tracks', 'account-albums', 'taxonomies', '$state'
(tracks, albums, taxonomies, state) ->
$.when.all [albums.refresh(), taxonomies.refresh(), tracks.getEdit(state.params.track_id, true)]
]
angular.module('ponyfm').controller "account-tracks-edit", [
'$scope', '$state', 'taxonomies', '$dialog', 'account-albums', 'account-tracks'
($scope, $state, taxonomies, $dialog, albums, tracks) ->
angular.module('ponyfm').controller "account-track", [
'$scope', '$state', 'taxonomies', '$dialog', 'account-albums', 'account-tracks', 'images'
($scope, $state, taxonomies, $dialog, albums, tracks, images) ->
$scope.isDirty = false
$scope.isSaving = false
$scope.taxonomies = taxonomies
@ -85,6 +85,7 @@ angular.module('ponyfm').controller "account-tracks-edit", [
trackDbItem.cover_url = track.real_cover_url
$scope.isDirty = false
$scope.errors = {}
images.refresh true
formData = new FormData();
_.each $scope.edit, (value, name) ->
@ -136,7 +137,7 @@ angular.module('ponyfm').controller "account-tracks-edit", [
$.post('/api/web/tracks/delete/' + track.id, {_token: window.pfm.token})
.then -> $scope.$apply ->
$scope.$emit 'track-deleted'
$state.transitionTo 'account-content.tracks'
$state.transitionTo 'account.tracks'
$scope.$on '$locationChangeStart', (e) ->
return if !$scope.isDirty

View file

@ -26,67 +26,8 @@ angular.module('ponyfm').controller "account-tracks", [
tracks.refresh().done setTracks
$scope.filters =
published: [
{title: 'Either', query: ''},
{title: 'Yes', query: 'published=1'},
{title: 'No', query: 'published=0'}]
sort: [
{title: 'Newest to Oldest', query: 'order=created_at,desc'},
{title: 'Oldest to Newest', query: 'order=created_at,asc'}]
genres: {}
trackTypes: {}
$scope.filter =
published: $scope.filters.published[0]
sort: $scope.filters.sort[0]
genres: {}
trackTypes: {}
$scope.titles =
genres: 'All'
trackTypes: 'All'
for genre in taxonomies.genres
$scope.filters.genres[genre.id] =
id: genre.id
title: genre.name
query: 'genres[]=' + genre.id
for type in taxonomies.trackTypes
$scope.filters.trackTypes[type.id] =
id: type.id
title: type.title
query: 'types[]=' + type.id
$scope.updateFilter = (type, filter) ->
$scope.filter[type] = filter
$scope.refreshList()
$scope.toggleFilter = (type, id) ->
if !$scope.filter[type][id]
$scope.filter[type][id] = $scope.filters[type][id]
else
delete $scope.filter[type][id]
length = _.keys($scope.filter[type]).length
if length == 1
$scope.titles[type] = _.map($scope.filter[type], (f) -> f.title).join ', '
else if length > 1
$scope.titles[type] = length + ' selected'
else
$scope.titles[type] = 'All'
$scope.refreshList()
$scope.refreshList = () ->
parts = [$scope.filter.sort.query, $scope.filter.published.query]
_.each $scope.filter.genres, (g) -> parts.push g.query
_.each $scope.filter.trackTypes, (g) -> parts.push g.query
query = parts.join '&'
tracks.refresh(query).done setTracks
tracks.refresh().done setTracks
$scope.selectTrack = (track) ->
$scope.data.selectedTrack = track

View file

@ -0,0 +1,12 @@
window.pfm.preloaders['favourites-albums'] = [
'favourites'
(favourites) ->
favourites.fetchAlbums(true)
]
angular.module('ponyfm').controller "favourites-albums", [
'$scope', 'favourites'
($scope, favourites) ->
favourites.fetchAlbums().done (res) ->
$scope.albums = res.albums
]

View file

@ -0,0 +1,12 @@
window.pfm.preloaders['favourites-playlists'] = [
'favourites'
(favourites) ->
favourites.fetchPlaylists(true)
]
angular.module('ponyfm').controller "favourites-playlists", [
'$scope', 'favourites'
($scope, favourites) ->
favourites.fetchPlaylists().done (res) ->
$scope.playlists = res.playlists
]

View file

@ -0,0 +1,12 @@
window.pfm.preloaders['favourites-tracks'] = [
'favourites'
(favourites) ->
favourites.fetchTracks(true)
]
angular.module('ponyfm').controller "favourites-tracks", [
'$scope', 'favourites'
($scope, favourites) ->
favourites.fetchTracks().done (res) ->
$scope.tracks = res.tracks
]

View file

@ -1,32 +0,0 @@
angular.module('ponyfm').controller "upload", [
'$scope', 'auth', 'upload', '$state'
($scope, auth, upload, $state) ->
$scope.$on 'upload-queue-started', () ->
$scope.state = 'uploading'
$scope.uploadDialogOpen = true
$scope.uploads = {}
$scope.progress = 0
$scope.uploadedFiles = 0
$scope.totalFiles = 0
$scope.$on 'upload-added', (e, upload) ->
$scope.uploads[upload.index] = upload
$scope.totalFiles++
$scope.$on 'upload-queue-ended', () ->
$scope.state = 'finished'
$scope.uploadDialogOpen = false if _.each upload.queue, (u) -> u.error == null
$state.transitionTo 'account-content.tracks'
$scope.uploadDialogOpen = false if !(_.size $scope.uploads)
$scope.$on 'upload-finished', (e, upload) ->
$scope.uploadedFiles++
delete $scope.uploads[upload.index] if upload.success
$scope.$on 'upload-progress', () ->
$scope.progress = upload.totalBytesUploaded / upload.totalBytes * 100
$scope.state = 'processing' if $scope.progress >= 100
$scope.close = () ->
$scope.uploadDialogOpen = false
]

View file

@ -0,0 +1,8 @@
angular.module('ponyfm').controller "uploader", [
'$scope', 'auth', 'upload', '$state'
($scope, auth, upload, $state) ->
$scope.data = upload
$scope.$on 'upload-finished', (e, upload) ->
]

View file

@ -41,7 +41,7 @@ angular.module('ponyfm').directive 'pfmPlayer', () ->
moveVolumeSlider = (absoluteY) ->
newY = absoluteY - $bar.offset().top;
maxY = $bar.height() - ($knob.height() / 2)
maxY = $bar.height() - ($knob.height() / 2) - 8
newY = 0 if newY < 0
newY = maxY if newY > maxY
@ -54,11 +54,11 @@ angular.module('ponyfm').directive 'pfmPlayer', () ->
e.preventDefault()
e.stopPropagation()
$slider.click (e) -> $scope.$apply -> moveVolumeSlider(e.pageY)
$slider.click (e) -> $scope.$apply -> moveVolumeSlider(e.pageY - 8)
$(document).mousemove (e) ->
return if !isSliding
moveVolumeSlider(e.pageY)
moveVolumeSlider(e.pageY - 8)
$knob.mousedown (e) ->
e.preventDefault()

View file

@ -43,7 +43,7 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
height = windowHeight - top;
return {
left: left - parentPosition.left - 5
left: left - parentPosition.left
top: top - parentPosition.top,
height: height - 15}

View file

@ -1,21 +1,19 @@
angular.module('ponyfm').directive 'uploader', [
'upload'
(upload) -> (scope) ->
$body = $ 'body'
$notice = $("<div class='file-over-notice'><p>Drop the files anywhere to begin your upload!</p></div>").appendTo($body)
notice = $notice[0]
(upload) -> (scope, element) ->
$dropzone = $(element)
window.addEventListener 'dragover', (e) ->
$dropzone[0].addEventListener 'dragover', (e) ->
e.preventDefault()
$body.addClass 'file-over'
$dropzone.addClass 'file-over'
notice.addEventListener 'dragleave', (e) ->
$dropzone[0].addEventListener 'dragleave', (e) ->
e.preventDefault()
$body.removeClass 'file-over'
$dropzone.removeClass 'file-over'
notice.addEventListener 'drop', (e) ->
$dropzone[0].addEventListener 'drop', (e) ->
e.preventDefault()
$body.removeClass 'file-over'
$dropzone.removeClass 'file-over'
files = e.target.files || e.dataTransfer.files
scope.$apply -> upload.upload files

View file

@ -1,6 +1,10 @@
angular.module('ponyfm').factory('favourites', [
'$rootScope', '$http'
($rootScope, $http) ->
tracksDef = null
playlistsDef = null
albumsDef = null
self =
toggle: (type, id) ->
def = new $.Deferred()
@ -9,5 +13,29 @@ angular.module('ponyfm').factory('favourites', [
def.promise()
fetchTracks: (force) ->
return tracksDef if !force && tracksDef
tracksDef = new $.Deferred()
$http.get('/api/web/favourites/tracks').success (res) ->
tracksDef.resolve res
tracksDef
fetchAlbums: (force) ->
return albumsDef if !force && albumsDef
albumsDef = new $.Deferred()
$http.get('/api/web/favourites/albums').success (res) ->
albumsDef.resolve res
albumsDef
fetchPlaylists: (force) ->
return playlistsDef if !force && playlistsDef
playlistsDef = new $.Deferred()
$http.get('/api/web/favourites/playlists').success (res) ->
playlistsDef.resolve res
playlistsDef
self
])

View file

@ -5,8 +5,8 @@ angular.module('ponyfm').factory('images', [
self =
images: []
isLoading: true
refresh: () ->
return def if def
refresh: (force) ->
return def if !force && def
def = new $.Deferred()
self.images = []

View file

@ -3,12 +3,8 @@ angular.module('ponyfm').factory('upload', [
($rootScope) ->
self =
queue: []
totalBytes: 0
totalBytesUploaded: 0
upload: (files) ->
$rootScope.$broadcast 'upload-queue-started' if self.queue.length == 0
_.each files, (file) ->
upload =
name: file.name
@ -21,7 +17,6 @@ angular.module('ponyfm').factory('upload', [
error: null
self.queue.push upload
self.totalBytes += file.size
$rootScope.$broadcast 'upload-added', upload
xhr = new XMLHttpRequest()
@ -29,7 +24,6 @@ angular.module('ponyfm').factory('upload', [
$rootScope.$apply ->
upload.uploadedSize = e.loaded
upload.progress = e.loaded / upload.size * 100
self.totalBytesUploaded = _.reduce self.queue, ((i, u) -> i + u.uploadedSize), 0
$rootScope.$broadcast 'upload-progress', upload
xhr.onload = -> $rootScope.$apply ->
@ -45,15 +39,9 @@ angular.module('ponyfm').factory('upload', [
$rootScope.$broadcast 'upload-error', [upload, error]
else
upload.success = true
upload.trackId = $.parseJSON(xhr.responseText).id
$rootScope.$broadcast 'upload-finished', upload
if (_.every self.queue, (u) -> !u.isUploading)
self.queue = []
self.totalBytes = 0
self.totalBytesUploaded = 0
$rootScope.$broadcast 'upload-queue-ended'
formData = new FormData();
formData.append('track', file);

View file

@ -1,2 +1,418 @@
@import-once 'base/bootstrap/bootstrap';
@import-once 'mixins';
ul.playlists {
overflow-y: auto;
margin: 0px;
padding: 0px;
list-style: none;
margin: 0px 5px;
margin-top: 10px;
li {
overflow: hidden;
margin: 0px;
padding: 0px;
height: 40px;
border-bottom: 1px solid #ddd;
img {
border-right: 1px solid #ddd;
padding: 0px;
display: block;
height: 40px;
width: 40px;
float: left;
}
.btn-group {
float: right;
display: block;
margin-top: 7px;
margin-right: 7px;
.btn {
.border-radius(0px);
}
}
a.main {
display: block;
text-decoration: none;
color: #444;
.is-public {
display: block;
float: right;
font-weight: bold;
margin-top: 10px;
margin-right: 10px;
font-size: 8pt;
}
.title {
.ellipsis();
margin-left: 50px;
line-height: 40px;
height: 40px;
display: block;
}
&:hover {
background: #eee;
}
}
}
}
.image-selector {
width: 500px;
max-height: 300px;
overflow-y: auto;
ul {
list-style: none;
padding: 0px;
margin: 0px;
li {
margin: 0px;
float: left;
width: 20%;
cursor: pointer;
img {
.transition(all 400ms);
display: block;
width: 100px;
height: 100px;
}
&:hover {
img {
opacity: .8;
}
}
}
}
}
html {
.two-pane-view.closed {
.account-albums-listing {
li {
.box-sizing(border-box);
width: 20%;
a {
padding: 15px;
.image {
.img-polaroid();
position: relative;
left: -5px;
width: 100%;
height: auto;
float: none;
}
.title {
margin: 0px;
font-size: 12pt;
padding: 0px;
margin-top: 5px;
}
.published {
margin: 0px;
padding: 0px;
}
}
}
}
}
}
.account-tracks-listing, .account-albums-listing {
overflow-y: auto;
margin: 0px;
padding: 0px;
list-style: none;
li {
.box-sizing(border-box);
padding: 0px;
margin: 0px;
line-height: normal;
&.empty {
.alert();
float: none !important;
width: auto !important;
display: block;
padding: 5px;
font-size: 9pt;
}
&.is-not-published a {
background: fadeout(@yellow, 90%);
}
&.selected, &.selected:hover {
a {
background: @pfm-purple;
cursor: default;
color: #fff;
.published {
color: #eee;
}
}
}
a {
.transition(350px ease-out all);
display: block;
margin: 0px;
padding: 5px;
font-size: 9pt;
font-weight: normal;
overflow: hidden;
.image {
width: 32px;
height: 32px;
float: left;
display: block;
}
.title {
.ellipsis();
display: block;
margin-left: 37px;
}
.published {
display: block;
color: #777;
margin-left: 37px;
font-size: 8pt;
}
&:hover {
background: #ddd;
text-decoration: none;
}
}
}
}
.two-pane-view {
.list {
.dropdowns {
margin-top: 0px;
}
}
.editor {
display: none;
.album-track-listing {
padding: 0px;
clear: both;
margin: 0px;
margin-top: 10px;
list-style: none;
li {
overflow: hidden;
line-height: normal;
padding: 0px;
margin: 0px;
font-size: 8pt;
border-bottom: 1px dashed #ddd;
div {
padding: 2px;
}
span {
display: block;
float: left;
margin-left: 5px;
margin-top: 2px;
}
.btn {
line-height: normal;
padding: 2px 5px;
margin: 0px;
}
&.ui-sortable-helper {
#gradient>.vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
border: none;
color: #fff;
}
&.ui-sortable-placeholder {
background: @yellow;
}
}
}
.show-songs, .album {
.btn {
display: block;
float: none;
}
}
.show-songs, .album, .track-selector {
.btn {
.border-radius(0px);
padding: 3px 10px;
font-size: 8pt;
text-align: left;
}
.error {
margin-top: 10px;
}
.pfm-popup {
width: 300px;
ul {
margin: 0px;
padding: 0px;
list-style: none;
li {
margin: 0px;
padding: 0px;
a {
.ellipsis();
display: block;
padding: 3px 10px;
font-size: 8pt;
color: #333333;
&:hover {
#gradient>.vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
text-decoration: none;
color: @dropdownLinkColorHover;
}
}
&.selected {
a {
#gradient>.vertical(@green, darken(@green, 5%));
color: #fff;
font-weight: bold;
&:hover {
#gradient>.vertical(fadeout(@green, 20%), fadeout(darken(@green, 5%), 20%));
}
}
}
}
}
}
}
}
&.closed {
.account-tracks-listing, .account-albums-listing {
.clearfix();
li {
float: left;
width: 25%;
&.empty {
}
}
}
}
&.open {
.list {
border-right: 2px solid #ddd;
width: 250px;
float: left;
}
.editor {
margin-left: 260px;
margin-right: 10px;
display: block;
.stretch-to-bottom {
padding-right: 10px;
}
}
}
}
.license-grid {
margin: 0px;
padding: 0px;
overflow: hidden;
list-style: none;
li {
float: left;
width: 25%;
> div {
margin: 0px 5px;
border: 1px solid #ddd;
padding: 10px;
cursor: pointer;
strong {
font-size: 9pt;
display: block;
margin: 0px;
margin-bottom: 5px;
padding: 0px;
line-height: normal;
}
}
p {
min-height: 120px;
font-size: 9pt;
}
a {
.border-radius(0px);
display: block;
width: auto;
}
&.selected {
> div {
cursor: default;
border-color: @blue;
}
}
&:hover > div {
border: 1px solid #3366CC;
}
&:first-child > div {
margin-left: 0px;
}
&:last-child > div {
margin-right: 0px;
}
}
}

View file

@ -13,3 +13,4 @@
@import 'player';
@import 'content';
@import 'dashboard';
@import 'uploader';

View file

@ -33,6 +33,50 @@ header {
text-decoration: none;
}
}
.user-details {
float: right;
margin-right: 10px;
margin-top: 9px;
.avatar {
.img-polaroid();
display: block;
float: right;
padding: 2px;
cursor: pointer;
&:hover {
background: #ddd;
}
span {
font-weight: bold;
display: inline-block;
padding: 0px 3px;
}
}
.dropdown-menu {
left: auto;
right: 0px;
}
.name {
float: left;
height: 44px;
line-height: 44px;
padding-right: 10px;
font-weight: bold;
font-size: 12pt;
}
img {
height: 40px;
width: 40px;
}
}
}
.now-playing {
@ -52,6 +96,7 @@ header {
padding: 0px;
margin: 0px;
font-size: 10pt;
position: relative;
li {
margin: 0px;
@ -128,6 +173,11 @@ header {
}
}
}
li.uploader {
a {
}
}
}
.site-content {

View file

@ -2,7 +2,7 @@
@import-once 'mixins';
.track-player {
overflow: hidden;
margin-right: 75px;
padding: 10px;
.image {
@ -106,6 +106,47 @@
color: #000;
}
}
&.volume {
position: relative;
.volume-slider {
display: none;
z-index: 1000;
position: absolute;
left: 0px;
top: 33px;
width: 100%;
height: 150px;
background: #ddd;
.bar {
background: @pfm-purple;
width: 10px;
margin: auto;
height: 100%;
position: relative;
}
.knob {
position: absolute;
top: 0px;
left: -3px;
background: darken(@pfm-purple, 20%);
width: 16px;
height: 16px;
}
}
&:hover, &.keep-open {
.volume-slider {
display: block;
}
background: #ddd;
}
}
}
}
}

View file

@ -11,6 +11,7 @@ html {
border-left: 3px solid #111;
.buttons {
padding: 5px;
background: #222;
.clear-button {
@ -18,6 +19,10 @@ html {
}
.open-button {
font-size: inherit;
padding: 2px 10px;
text-indent: 0px;
i:before {
content: "\f077";
}
@ -42,7 +47,6 @@ html {
color: #fff;
.buttons {
padding: 5px;
overflow: hidden;
> a {
@ -60,6 +64,13 @@ html {
margin-right: 5px;
display: none;
}
.open-button {
font-size: 1pt;
padding: 2px;
text-indent: -1000px;
overflow: hidden;
}
}
ul {

View file

@ -0,0 +1,82 @@
@import-once 'variables';
.uploader {
h1 {
margin: 10px 0px;
}
.dropzone {
border: 2px dotted @pfm-purple;
background: lighten(@pfm-purple, 25%);
padding: 10px;
color: darken(@pfm-purple, 25%);
margin-bottom: 10px;
p {
padding: 0px;
margin: 0px;
}
&.file-over {
background: lighten(@pfm-purple, 5%);
}
}
.close-button {
.border-radius(0px);
width: auto;
margin-bottom: 10px;
float: none;
display: block;
}
.uploads {
margin: 0px;
padding: 0px;
list-style: none;
li {
margin-bottom: 5px;
background: #eee;
font-size: 9pt;
position: relative;
z-index: 1;
color: #444;
border: 2px solid #aaa;
p {
margin: 5px;
padding: 0px;
height: 26px;
line-height: 26px;
a {
}
}
.bar {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
z-index: -1;
}
&.has-error {
border-color: @red;
.bar {
display: none;
}
}
&.is-processing {
border-color: @blue;
.bar {
display: none;
}
}
}
}
}

View file

@ -0,0 +1,8 @@
<ul class="tabs">
<li ng-class="{active: stateIncludes('account.tracks')}"><a href="/account/tracks">Tracks</a></li>
<li ng-class="{active: stateIncludes('account.albums')}"><a href="/account/albums">Albums</a></li>
<li ng-class="{active: stateIncludes('account.playlists')}"><a href="/account/playlists">Playlists</a></li>
<li ng-class="{active: stateIncludes('account.settings')}"><a href="/account">Settings</a></li>
</ul>
<ui-view></ui-view>

View file

@ -1,8 +0,0 @@
<div>
<ul class="tabs">
<li ng-class="{active: stateIncludes('account-content.tracks')}"><a href="/account/tracks">Tracks</a></li>
<li ng-class="{active: stateIncludes('account-content.albums')}"><a href="/account/albums">Albums</a></li>
</ul>
<ui-view></ui-view>
</div>

View file

@ -1,25 +0,0 @@
<div>
<h1>Your Playlists</h1>
<ul class="playlists stretch-to-bottom">
<li ng-repeat="playlist in playlists">
<div class="btn-group">
<a href="#" pfm-eat-click class="btn btn-small" ng-click="togglePlaylistPin(playlist)" ng-class="{active: playlist.is_pinned, 'btn-primary': playlist.is_pinned}">
<i class="icon-pushpin"></i>
</a>
<a href="#" pfm-eat-click class="btn btn-small" ng-click="editPlaylist(playlist)">Edit</a>
<a href="#" pfm-eat-click class="btn btn-small btn-danger" ng-click="deletePlaylist(playlist)">Delete</a>
</div>
<a href="{{playlist.url}}" class="main">
<span class="is-public">
<span ng-show="playlist.is_public">Is Public</span>
<span ng-hide="playlist.is_public">Is Private</span>
</span>
<img ng-src="{{playlist.covers.small}}" />
<span class="title">
{{playlist.title}}
</span>
</a>
</li>
</ul>
</div>

View file

@ -1,60 +0,0 @@
<div>
<ul class="dropdowns">
<li class="dropdown">
<a class="dropdown-toggle btn">
Published: <strong>{{filter.published.title}}</strong>
</a>
<ul class="dropdown-menu">
<li ng-repeat="filter in filters.published">
<a pfm-eat-click href="#" ng-click="updateFilter('published', filter)">{{filter.title}}</a>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle btn">
Sort: <strong>{{filter.sort.title}}</strong>
</a>
<ul class="dropdown-menu">
<li ng-repeat="filter in filters.sort">
<a pfm-eat-click href="#" ng-click="updateFilter('sort', filter)">{{filter.title}}</a>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle btn">
Type: <strong>{{titles.trackTypes}}</strong>
</a>
<ul class="dropdown-menu">
<li ng-repeat="type in filters.trackTypes" ng-class="{selected: filter.trackTypes[type.id]}">
<a pfm-eat-click href="#" ng-click="toggleFilter('trackTypes', type.id); $event.stopPropagation();">{{type.title}}</a>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle btn">
Genera: <strong>{{titles.genres}}</strong>
</a>
<ul class="dropdown-menu">
<li ng-repeat="genre in filters.genres" ng-class="{selected: filter.genres[genre.id]}">
<a pfm-eat-click href="#" ng-click="toggleFilter('genres', genre.id); $event.stopPropagation();">{{genre.title}}</a>
</li>
</ul>
</li>
</ul>
<div class="two-pane-view" ng-class="{open: data.selectedTrack != null, closed: data.selectedTrack == null}">
<div class="list">
<ul class="account-tracks-listing stretch-to-bottom">
<li ng-repeat="track in tracks" ng-class="{selected: track.id == data.selectedTrack.id, 'is-not-published': !track.is_published}">
<a href="/account/tracks/edit/{{track.id}}" ng-click="selectTrack(track)">
<img class="image" ng-src="{{track.cover_url}}" />
<span class="title">{{track.title}}</span>
<span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span>
</a>
</li>
</ul>
</div>
<ui-view class="editor"></ui-view>
</div>
</div>

View file

@ -1,14 +0,0 @@
<div>
<ul class="tabs">
<li ng-class="{active: stateIncludes('account-favourites.tracks')}"><a href="/account/favourites">Tracks</a></li>
<li ng-class="{active: stateIncludes('account-favourites.albums')}"><a href="/account/favourites/albums">Albums</a></li>
<li ng-class="{active: stateIncludes('account-favourites.playlists')}"><a href="/account/favourites/playlists">Playlists</a></li>
</ul>
<ui-view></ui-view>
<pre>
$state = {{$state.current.name}}
$stateParams = {{$stateParams}}
</pre>
</div>

View file

@ -1 +0,0 @@
<h2>Favourite Albums</h2>

View file

@ -1 +0,0 @@
<h2>Favourite Playlists</h2>

View file

@ -1 +0,0 @@
<h2>Favourite tracks</h2>

View file

@ -0,0 +1,22 @@
<ul class="playlists stretch-to-bottom">
<li ng-repeat="playlist in playlists">
<div class="btn-group">
<a href="#" pfm-eat-click class="btn btn-small" ng-click="togglePlaylistPin(playlist)" ng-class="{active: playlist.is_pinned, 'btn-primary': playlist.is_pinned}">
<i class="icon-pushpin"></i>
</a>
<a href="#" pfm-eat-click class="btn btn-small" ng-click="editPlaylist(playlist)">Edit</a>
<a href="#" pfm-eat-click class="btn btn-small btn-danger" ng-click="deletePlaylist(playlist)">Delete</a>
</div>
<a href="{{playlist.url}}" class="main">
<span class="is-public">
<span ng-show="playlist.is_public">Is Public</span>
<span ng-hide="playlist.is_public">Is Private</span>
</span>
<img ng-src="{{playlist.covers.small}}" />
<span class="title">
{{playlist.title}}
</span>
</a>
</li>
</ul>

View file

@ -1,6 +1,4 @@
<div>
<h1>Account Settings</h1>
<form ng-submit="updateAccount()" class="pfm-form account-settings-form">
<form ng-submit="updateAccount()" class="pfm-form account-settings-form">
<ul class="toolbar">
<li>
<button type="submit" class="btn" ng-class="{disabled: !isDirty || isSaving, 'btn-primary': isDirty}">
@ -10,25 +8,22 @@
</li>
</ul>
<div class="stretch-to-bottom">
<div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.display_name != null}">
<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 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 class="form-row">
<label for="can_see_explicit_content" class="strong"><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}">
<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>
<div class="form-row span6" ng-class="{'has-error': errors.avatar != null || errors.gravatar != null}">
<label for="uses_gravatar">
<div class="form-row" ng-class="{'has-error': errors.avatar != null || errors.gravatar != null}">
<label for="uses_gravatar" class="strong">
<input ng-change="touchModel()" ng-disabled="isLoading" id="uses_gravatar" type="checkbox" ng-model="settings.uses_gravatar" /> Use Gravatar
</label>
<div ng-show="!settings.uses_gravatar">
@ -38,7 +33,4 @@
<div class="error" ng-show="errors.avatar != null">{{errors.avatar}}</div>
<div class="error" ng-show="errors.gravatar != null">{{errors.gravatar}}</div>
</div>
</div>
</div>
</form>
</div>
</form>

View file

@ -0,0 +1,15 @@
<div class="two-pane-view" ng-class="{open: data.selectedTrack != null, closed: data.selectedTrack == null}">
<div class="list">
<ul class="account-tracks-listing stretch-to-bottom">
<li ng-repeat="track in tracks" ng-class="{selected: track.id == data.selectedTrack.id, 'is-not-published': !track.is_published}">
<a href="/account/tracks/edit/{{track.id}}" ng-click="selectTrack(track)">
<img class="image" ng-src="{{track.cover_url}}" />
<span class="title">{{track.title}}</span>
<span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span>
</a>
</li>
</ul>
</div>
<ui-view class="editor"></ui-view>
</div>

View file

@ -1,3 +1,3 @@
<div class="stretch-to-bottom">
<pfm-albums-list albums="albums" />
<pfm-albums-list albums="albums"></pfm-albums-list>
</div>

View file

@ -10,6 +10,7 @@
</li>
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share or Embed</a></li>
<li><pfm-favourite-button resource="album" type="album"></pfm-favourite-button></li>
<li bo-show="album.permissions.edit"><a class="btn btn-small" bo-href="'/account/albums/edit/' + album.id">Edit</a></li>
</ul>
<header>

View file

@ -12,7 +12,7 @@
</a>
</li>
<li><a pfm-eat-click ng-click="playNext()" class="next" href="#"><i class="icon-fast-forward"></i></a></li>
<li>
<li class="volume">
<a pfm-eat-click ng-click="" class="volume" href="#">
<i class="icon-volume-up"></i>
</a>

View file

@ -0,0 +1,14 @@
<div>
<ul class="tabs">
<li ng-class="{active: stateIncludes('favourites.tracks')}"><a href="/account/favourites/tracks">Tracks</a></li>
<li ng-class="{active: stateIncludes('favourites.albums')}"><a href="/account/favourites/albums">Albums</a></li>
<li ng-class="{active: stateIncludes('favourites.playlists')}"><a href="/account/favourites/playlists">Playlists</a></li>
</ul>
<ui-view></ui-view>
<pre>
$state = {{$state.current.name}}
$stateParams = {{$stateParams}}
</pre>
</div>

View file

@ -0,0 +1,3 @@
<div class="stretch-to-bottom">
<pfm-albums-list albums="albums" />
</div>

View file

@ -0,0 +1,3 @@
<div class="stretch-to-bottom">
<pfm-playlists-list playlists="playlists"></pfm-playlists-list>
</div>

View file

@ -1,19 +0,0 @@
<div class="overlay upload-dialog" ng-controller="upload" ng-show="uploadDialogOpen">
<div class="inner">
<h2 ng-show="state == 'uploading'">
<span>{{uploadedFiles}} of {{totalFiles}}</span>
Uploading Tracks...
</h2>
<h2 ng-show="state == 'processing'">Processing...</h2>
<a href="#" pfm-eat-click class="btn btn-primary close-button" ng-click="close()" ng-show="state == 'finished'">Close <i class="icon-remove"></i></a>
<ul class="uploads">
<li ng-repeat="upload in uploads" ng-class="{'has-error': upload.error != null, 'is-processing': upload.isUploading && upload.error == null && upload.progress >= 100}" ng-animate="'upload-queue'">
<p><strong>{{upload.name}}</strong></p>
<p ng-show="upload.error != null">{{upload.error}}</p>
<div class="bar" pfm-progress-bar="upload.progress"></div>
</li>
</ul>
</div>
</div>

View file

@ -1,3 +1,3 @@
<div class="stretch-to-bottom">
<pfm-playlists-list playlists="playlists" />
<pfm-playlists-list playlists="playlists"></pfm-playlists-list>
</div>

View file

@ -0,0 +1,3 @@
<div class="stretch-to-bottom">
<pfm-tracks-list tracks="tracks" class="three-columns"></pfm-tracks-list>
</div>

View file

@ -24,6 +24,7 @@
</li>
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
<li><pfm-favourite-button resource="track" type="track"></pfm-favourite-button></li>
<li bo-show="track.permissions.edit"><a class="btn btn-small" bo-href="'/account/tracks/edit/' + track.id">Edit</a></li>
</ul>
<header>

View file

@ -0,0 +1,25 @@
<div class="uploader">
<div class="dropzone" uploader>
<p>Drop files here to begin your upload!</p>
</div>
<ul class="uploads">
<li ng-repeat="upload in data.queue" ng-class="{'has-error': upload.error != null, 'is-processing': upload.isUploading && upload.error == null && upload.progress >= 100}" ng-animate="'upload-queue'">
<p>
<span ng-show="!upload.success">
<strong ng-show="upload.isUploading && upload.error == null && upload.progress >= 100">Processing</strong>
<strong ng-hide="upload.isUploading && upload.error == null && upload.progress >= 100">Uploading</strong>
{{upload.name}}
</span>
<span ng-show="upload.success">
<a href="/account/tracks/edit/{{upload.trackId}}" class="btn btn-small btn-primary">
Publish
</a>
{{upload.name}}
</span>
</p>
<p ng-show="upload.error != null">{{upload.error}}</p>
<div class="bar" pfm-progress-bar="upload.progress"></div>
</li>
</ul>
</div>