Intermediate commit (power is going on and off)

This commit is contained in:
nelsonlaquet 2013-07-31 18:04:04 -05:00
parent 8d3d126550
commit 21009713f3
21 changed files with 386 additions and 118 deletions

View file

@ -26,6 +26,14 @@
return $this->execute(new EditTrackCommand($id, Input::all()));
}
public function getShow($id) {
$track = Track::find($id);
if (!$track || !$track->canView(Auth::user()))
return $this->notFound('Track not found!');
return Response::json(['track' => Track::mapPublicTrackShow($track)], 200);
}
public function getRecent() {
$query = Track::summary()->with(['genre', 'user', 'cover'])->whereNotNull('published_at')->orderBy('published_at', 'desc')->take(15);
if (!Auth::check() || !Auth::user()->can_see_explicit_content)
@ -34,7 +42,7 @@
$tracks = [];
foreach ($query->get() as $track) {
$tracks[] = $this->mapPublicTrack($track);
$tracks[] = Track::mapPublicTrackSummary($track);
}
return Response::json($tracks, 200);
@ -51,11 +59,10 @@
$totalCount = $query->count();
$query->take(30)->skip(30 * ($page - 1));
$tracks = [];
foreach ($query->get() as $track) {
$tracks[] = $this->mapPublicTrack($track);
}
$tracks = [];
foreach ($query->get() as $track)
$tracks[] = Track::mapPublicTrackSummary($track);
return Response::json(["tracks" => $tracks, "current_page" => $page, "total_pages" => ceil($totalCount / 30)], 200);
}
@ -73,27 +80,9 @@
$this->applyFilters($query);
$dbTracks = $query->get();
$tracks = [];
foreach ($dbTracks as $track) {
$tracks[] = [
'id' => $track->id,
'title' => $track->title,
'user_id' => $track->user_id,
'slug' => $track->slug,
'is_vocal' => $track->is_vocal,
'is_explicit' => $track->is_explicit,
'is_downloadable' => $track->is_downloadable,
'is_published' => $track->isPublished(),
'created_at' => $track->created_at,
'published_at' => $track->published_at,
'duration' => $track->duration,
'genre_id' => $track->genre_id,
'track_type_id' => $track->track_type_id,
'cover_url' => $track->getCoverUrl(Image::SMALL)
];
}
foreach ($query->get() as $track)
$tracks[] = Track::mapPrivateTrackSummary($track);
return Response::json($tracks, 200);
}
@ -106,68 +95,9 @@
if ($track->user_id != Auth::user()->id)
return $this->notAuthorized();
$showSongs = [];
foreach ($track->showSongs as $showSong) {
$showSongs[] = ['id' => $showSong->id, 'title' => $showSong->title];
}
return Response::json([
'id' => $track->id,
'title' => $track->title,
'user_id' => $track->user_id,
'slug' => $track->slug,
'is_vocal' => (bool)$track->is_vocal,
'is_explicit' => (bool)$track->is_explicit,
'is_downloadable' => !$track->isPublished() ? true : (bool)$track->is_downloadable,
'is_published' => $track->published_at != null,
'created_at' => $track->created_at,
'published_at' => $track->published_at,
'duration' => $track->duration,
'genre_id' => $track->genre_id,
'track_type_id' => $track->track_type_id,
'license_id' => $track->license_id != null ? $track->license_id : 3,
'description' => $track->description,
'lyrics' => $track->lyrics,
'released_at' => $track->released_at,
'cover_url' => $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null,
'real_cover_url' => $track->getCoverUrl(Image::NORMAL),
'show_songs' => $showSongs,
'album_id' => $track->album_id
], 200);
return Response::json(Track::mapPrivateTrackShow($track), 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')) {

View file

@ -7,4 +7,23 @@
public function getIndex() {
return View::make('tracks.index');
}
public function getTrack($id, $slug) {
$track = Track::find($id);
if (!$track || !$track->canView(Auth::user()))
App::abort(404);
if ($track->slug != $slug)
return Redirect::action('TracksController@getTrack', [$id, $track->slug]);
return View::make('tracks.show');
}
public function getShortlink($id) {
$track = Track::find($id);
if (!$track || !$track->canView(Auth::user()))
App::abort(404);
return Redirect::action('TracksController@getTrack', [$id, $track->slug]);
}
}

View file

@ -39,7 +39,7 @@
}
public function getUrlAttribute() {
return '/playlist/' . $this->id . '/' . $this->slug;
return '/playlist/' . $this->id . '-' . $this->slug;
}
public function getCoverUrl($type = Image::NORMAL) {

View file

@ -28,6 +28,84 @@
return self::select('id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at', 'published_at', 'duration', 'is_downloadable', 'genre_id', 'track_type_id', 'cover_id', 'album_id');
}
public static function mapPublicTrackShow($track) {
$returnValue = self::mapPublicTrackSummary($track);
$returnValue['description'] = $track->description;
$returnValue['lyrics'] = $track->lyrics;
return $returnValue;
}
public static function mapPublicTrackSummary($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)
]
];
}
public static function mapPrivateTrackShow($track) {
$showSongs = [];
foreach ($track->showSongs as $showSong) {
$showSongs[] = ['id' => $showSong->id, 'title' => $showSong->title];
}
$returnValue = self::mapPrivateTrackSummary($track);
$returnValue['album_id'] = $track->album_id;
$returnValue['show_songs'] = $showSongs;
$returnValue['real_cover_url'] = $track->getCoverUrl(Image::NORMAL);
$returnValue['cover_url'] = $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null;
$returnValue['released_at'] = $track->released_at;
$returnValue['lyrics'] = $track->lyrics;
$returnValue['description'] = $track->description;
$returnValue['is_downloadable'] = !$track->isPublished() ? true : (bool)$track->is_downloadable;
$returnValue['license_id'] = $track->license_id != null ? $track->license_id : 3;
return $returnValue;
}
public static function mapPrivateTrackSummary($track) {
return [
'id' => $track->id,
'title' => $track->title,
'user_id' => $track->user_id,
'slug' => $track->slug,
'is_vocal' => $track->is_vocal,
'is_explicit' => $track->is_explicit,
'is_downloadable' => $track->is_downloadable,
'is_published' => $track->isPublished(),
'created_at' => $track->created_at,
'published_at' => $track->published_at,
'duration' => $track->duration,
'genre_id' => $track->genre_id,
'track_type_id' => $track->track_type_id,
'cover_url' => $track->getCoverUrl(Image::SMALL)
];
}
protected $table = 'tracks';
public function genre() {
@ -54,8 +132,15 @@
return date('Y', strtotime($this->release_date));
}
public function canView($user) {
if ($this->isPublished())
return true;
return $this->user_id == $user->id;
}
public function getUrlAttribute() {
return URL::to('/tracks/' . $this->id . '/' . $this->slug);
return URL::to('/tracks/' . $this->id . '-' . $this->slug);
}
public function getReleaseDate() {

View file

@ -16,6 +16,9 @@
Route::get('/tracks/popular', 'TracksController@getIndex');
Route::get('/tracks/random', 'TracksController@getIndex');
Route::get('tracks/{id}-{slug}', 'TracksController@getTrack');
Route::get('t{id}', 'TracksController@getShortlink' );
Route::get('/albums', 'AlbumsController@getIndex');
Route::get('/artists', 'ArtistsController@getIndex');
Route::get('/playlists', 'PlaylistsController@getIndex');
@ -30,7 +33,6 @@
Route::get('u{id}/avatar_{type}.png', 'UsersController@getAvatar');
Route::get('playlist/{id}/{slug}', 'PlaylistsController@getPlaylist');
Route::get('playlist/{id}-{slug}', 'PlaylistsController@getPlaylist');
Route::get('p{id}', 'PlaylistsController@getShortlink');
@ -41,6 +43,7 @@
Route::get('/tracks/recent', 'Api\Web\TracksController@getRecent');
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
Route::get('/dashboard', 'Api\Web\DashboardController@getIndex');

View file

@ -35,7 +35,7 @@
<li ng-class="{selected: $state.includes('home')}"><a href="/">Home</a></li>
@endif
<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('tracks') || $state.includes('track')}"><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>
<li ng-class="{selected: $state.includes('playlists')}"><a href="/playlists">Playlists <i class="icon-music"></i></a></li>
<li ng-class="{selected: $state.includes('artists')}"><a href="/artists">Artists <i class="icon-user"></i></a></li>

View file

@ -0,0 +1,6 @@
@extends('shared._app_layout')
@section('app_content')
<h1>Track Listing!</h1>
<p>This page should be what search engines see</p>
@endsection

View file

@ -67,6 +67,11 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
# Tracks
state.state 'track',
url: '/tracks/{id:[^\-]+}-{slug}'
templateUrl: '/templates/tracks/show.html'
controller: 'track'
state.state 'tracks',
url: '/tracks'
templateUrl: '/templates/tracks/_layout.html'
@ -104,7 +109,7 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
templateUrl: '/templates/playlists/index.html'
state.state 'playlist',
url: '/playlist/:id/:slug'
url: '/playlist/{id:[^\-]+}-{slug}'
templateUrl: '/templates/playlists/show.html'
controller: 'playlist'

View file

@ -1,6 +1,7 @@
angular.module('ponyfm').controller 'playlist', [
'$scope', '$state'
($scope, $state) ->
console.log $state.params.id
$scope.refresh = () ->
$.getJSON('/api/web/playlists/show/' + $state.params.id)
.done (playlist) -> $scope.$apply ->

View file

@ -0,0 +1,12 @@
window.pfm.preloaders['track'] = [
'tracks', '$state'
(tracks, $state) ->
tracks.fetch $state.params.id
]
angular.module('ponyfm').controller "track", [
'$scope', 'tracks', '$state'
($scope, tracks, $state) ->
tracks.fetch($state.params.id).done (trackResponse) ->
$scope.track = trackResponse.track
]

View file

@ -1,14 +1,14 @@
window.pfm.preloaders['tracks-list'] = [
'tracks', '$state'
(tracks, $state) ->
$.when.all [tracks.loadFilters().then(->
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", [

View file

@ -1,3 +1,9 @@
window.pfm.preloaders['tracks'] = [
'tracks', '$state'
(tracks) ->
tracks.loadFilters()
]
angular.module('ponyfm').controller "tracks", [
'$scope', 'tracks', '$state'
($scope, tracks, $state) ->
@ -30,4 +36,6 @@ angular.module('ponyfm').controller "tracks", [
$scope.gotoPage = (page) ->
$state.transitionTo 'tracks.search.list', {filter: $state.params.filter, page: page}
$scope.$on '$destroy', -> tracks.mainQuery = tracks.createQuery()
]

View file

@ -0,0 +1,2 @@
angular.module('ponyfm').filter 'newlines', () ->
(input) -> input.replace(/\n/g, '<br/>')

View file

@ -0,0 +1,5 @@
angular.module('ponyfm').filter 'noHTML', () ->
(input) ->
input.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')

View file

@ -1,7 +1,8 @@
angular.module('ponyfm').factory('tracks', [
'$rootScope', '$http', 'taxonomies'
($rootScope, $http, taxonomies) ->
def = null
filterDef = null
trackCache = {}
class Query
cachedDef: null
@ -97,7 +98,7 @@ angular.module('ponyfm').factory('tracks', [
fetch: () ->
return @cachedDef if @cachedDef
@cachedDef = new $.Deferred()
def = @cachedDef
trackDef = @cachedDef
query = '/api/web/tracks?'
parts = ['page=' + @page]
@ -115,18 +116,28 @@ angular.module('ponyfm').factory('tracks', [
for listener in @listeners
listener tracks
def.resolve tracks
trackDef.resolve tracks
def.promise()
trackDef.promise()
self =
filters: {}
createQuery: -> new Query self.filters
loadFilters: ->
return def if def
fetch: (id, force) ->
force = force || false
return trackCache[id] if !force && trackCache[id]
trackDef = new $.Deferred()
$http.get('/api/web/tracks/' + id).success (track) ->
trackDef.resolve track
def = new $.Deferred()
trackCache[id] = trackDef.promise()
createQuery: -> new Query self.filters
loadFilters: ->
return filterDef if filterDef
filterDef = new $.Deferred()
self.filters.isVocal =
type: 'single'
values: [
@ -174,9 +185,9 @@ angular.module('ponyfm').factory('tracks', [
id: song.id
self.mainQuery = self.createQuery()
def.resolve self
filterDef.resolve self
def.promise()
filterDef.promise()
self
])

View file

@ -9,6 +9,17 @@ window.handleResize = () ->
if newHeight > 0
$this.height newHeight
$('.revealable').each () ->
$this = $ this
$this.data 'real-height', $this.height()
$this.css
height: '15em'
$this.find('.reveal').click (e) ->
e.preventDefault()
$this.css {height: 'auto'}
$(this).fadeOut 200
window.alignVertically = (element) ->
$element = $(element)
$parent = $element.parent()

View file

@ -98,11 +98,11 @@ html .dropdown-menu {
li.selected {
a {
background: @green;
background: @blue;
color: #fff;
&:hover {
background: fadeout(@green, 20%);
background: fadeout(@blue, 20%);
}
}
}
@ -191,22 +191,51 @@ html .dropdown-menu {
}
html {
.breadcrumb {
.border-radius(0px);
background: #eee;
margin-bottom: 10px;
font-size: 10pt;
}
.pagination {
border: none;
background: #ccc;
margin: 5px 0px;
padding: 0px;
overflow: hidden;
ul {
.border-radius(0px);
margin: 5px;
display: block;
overflow: hidden;
> li:first-child > a, > li:last-child > a {
.border-radius(0px);
}
li {
display: block;
float: left;
a {
.border-radius(0px);
border: none;
padding: 1px 10px;
display: block;
float: left;
}
}
li.active a {
background: #444;
color: #ddd;
}
}
&.pagination-right {
float: right;
}
li a {
.border-radius(0px);
border: none;
padding: 1px 10px;
}
li.active a {
background: #444;
color: #ddd;
}
}
}

View file

@ -403,12 +403,11 @@ header {
}
.site-content {
h1 {
h1, h2 {
border: none;
background: none;
color: #747474;
font-weight: 300;
font-size: 24px;
line-height: normal;
font-weight: normal;
margin: 0px;
@ -421,6 +420,27 @@ header {
margin-top: 8px;
text-decoration: none;
}
.subtitle {
font-size: 10pt;
display: block;
padding: 3px 0px;
a {
float: none;
display: inline;
}
}
}
h1 {
font-size: 24px;
}
h2 {
font-size: 20px;
border-bottom: 2px solid #ddd;
margin-bottom: 2px;
}
> div > .tabs {
@ -606,4 +626,8 @@ html {
.upload-queue-leave {
.transform(scale(1));
}
.stretch-to-bottom {
overflow-y: auto;
}

View file

@ -125,4 +125,64 @@
}
}
}
}
.track-details {
.cover-image {
img {
.img-polaroid();
}
}
.lyrics {
position: relative;
overflow: hidden;
padding: 0px;
margin: 0px;
h2 {
}
.reveal {
#gradient>.vertical(rgba(255,255,255,0), rgba(255,255,255,1));
.box-sizing(border-box);
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 400;
border: 2px solid #fff;
cursor: pointer;
&:hover {
border: 2px solid @blue;
}
a {
.box-sizing(border-box);
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
background: #eee;
padding: 3px;
z-index: 500;
text-decoration: none;
font-size: 10pt;
}
}
}
}
html {
.track-toolbar {
background: #eee;
padding: 5px;
> .btn:first-child, > .btn:last-child {
.border-radius(0px);
}
}
}

View file

@ -54,7 +54,7 @@
</li>
</ul>
<div class="pagination" ng-show="totalPages > 0">
<div class="pagination" ng-show="totalPages > 1">
<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}">

View file

@ -0,0 +1,57 @@
<div>
<ul class="breadcrumb">
<li><a href="/tracks">Tracks</a> <span class="divider">/</span></li>
<li><a href="/tracks?filter=genres-{{track.genre.id}}">{{track.genre.name}}</a> <span class="divider">/</span></li>
<li class="active">{{track.title}}</li>
</ul>
<div class="track-toolbar btn-group pull-right">
<a href="#" class="btn btn-small">
Favourite This!
<i class="icon-star-empty"></i>
</a>
<a href="#" class="btn btn-small">
Add to Playlist <i class="caret"></i>
</a>
<div class="dropdown">
<a href="#" class="btn btn-small btn-info dropdown-toggle">
Downloads <i class="caret"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#"></a></li>
</ul>
</div>
</div>
<h1>
{{track.title}}
<span class="subtitle">
by: <a href="{{track.user.url}}">{{track.user.name}}</a>
</span>
</h1>
<div class="track-details">
<div class="row-fluid">
<div class="span7">
<div class="description">
<p ng-bind-html-unsafe="track.description | noHTML | newlines"></p>
</div>
<div ng-show="track.is_vocal && track.lyrics.length" class="lyrics-panel">
<h2>Lyrics</h2>
<div class="lyrics revealable">
<div class="reveal">
<a href="#">Click to reveal full lyrics...</a>
</div>
<p class="content" ng-bind-html-unsafe="track.lyrics | noHTML | newlines"></p>
</div>
</div>
</div>
<div class="span5 cover-image">
<img ng-src="{{track.covers.normal}}" />
</div>
</div>
<h2>Comments</h2>
</div>
</div>