diff --git a/.editorconfig b/.editorconfig index c5eb0981..d646d1d0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,11 +9,14 @@ end_of_line = lf insert_final_newline = true # 4 space indentation and default charset -[*.{php,coffee,js,less,css,html}] +[*.{php,coffee,js,html}] charset = utf-8 indent_style = space indent_size = 4 +[*.{less,css}] +indent_size = 2 + # One-offs [{package.json}] diff --git a/app/Commands/EditTrackCommand.php b/app/Commands/EditTrackCommand.php index f1629632..a8b97c13 100644 --- a/app/Commands/EditTrackCommand.php +++ b/app/Commands/EditTrackCommand.php @@ -23,6 +23,7 @@ namespace Poniverse\Ponyfm\Commands; use Poniverse\Ponyfm\Album; use Poniverse\Ponyfm\Image; use Poniverse\Ponyfm\Track; +use Poniverse\Ponyfm\TrackType; use Poniverse\Ponyfm\User; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -65,7 +66,7 @@ class EditTrackCommand extends CommandBase 'license_id' => 'required|exists:licenses,id', 'genre_id' => 'required|exists:genres,id', 'cover' => 'image|mimes:png|min_width:350|min_height:350', - 'track_type_id' => 'required|exists:track_types,id', + 'track_type_id' => 'required|exists:track_types,id|not_in:'.TrackType::UNCLASSIFIED_TRACK, 'songs' => 'required_when:track_type,2|exists:songs,id', 'cover_id' => 'exists:images,id', 'album_id' => 'exists:albums,id' @@ -121,7 +122,7 @@ class EditTrackCommand extends CommandBase $track->album_id = null; } - if ($track->track_type_id == 2) { + if ($track->track_type_id == TrackType::OFFICIAL_TRACK_REMIX) { $track->showSongs()->sync(explode(',', $this->_input['show_song_ids'])); } else { $track->showSongs()->sync([]); diff --git a/app/Console/Commands/PublishUnclassifiedMlpmaTracks.php b/app/Console/Commands/PublishUnclassifiedMlpmaTracks.php new file mode 100644 index 00000000..215a354d --- /dev/null +++ b/app/Console/Commands/PublishUnclassifiedMlpmaTracks.php @@ -0,0 +1,72 @@ +. + */ + +namespace Poniverse\Ponyfm\Console\Commands; + +use Carbon\Carbon; +use DB; +use Illuminate\Console\Command; +use Poniverse\Ponyfm\Track; +use Poniverse\Ponyfm\TrackType; + +class PublishUnclassifiedMlpmaTracks extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'mlpma:declassify'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'This publishes all unpublished MLPMA tracks as the "unclassified" track type.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $affectedTracks = Track::mlpma()-> + whereNull('published_at') + ->update([ + 'track_type_id' => TrackType::UNCLASSIFIED_TRACK, + 'published_at' => DB::raw('released_at'), + 'updated_at' => Carbon::now(), + ]); + + $this->info("Updated ${affectedTracks} tracks."); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 67e02105..9f97521d 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -35,6 +35,7 @@ class Kernel extends ConsoleKernel \Poniverse\Ponyfm\Console\Commands\RefreshCache::class, \Poniverse\Ponyfm\Console\Commands\ImportMLPMA::class, \Poniverse\Ponyfm\Console\Commands\ClassifyMLPMA::class, + \Poniverse\Ponyfm\Console\Commands\PublishUnclassifiedMlpmaTracks::class, \Poniverse\Ponyfm\Console\Commands\RebuildTags::class, \Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class, \Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class, diff --git a/app/Http/Controllers/Api/V1/TracksController.php b/app/Http/Controllers/Api/V1/TracksController.php index 16fe5af0..773b13dc 100644 --- a/app/Http/Controllers/Api/V1/TracksController.php +++ b/app/Http/Controllers/Api/V1/TracksController.php @@ -100,7 +100,11 @@ class TracksController extends \ApiControllerBase 'small' => $track->getCoverUrl(Image::SMALL), 'normal' => $track->getCoverUrl(Image::NORMAL) ], - 'comments' => $comments + 'comments' => $comments, + + // As of 2015-10-28, this should be expected to produce either + // "direct_upload" or "mlpma" for all tracks. + 'source' => $track->source ], 200); } } diff --git a/app/Http/Controllers/Api/Web/TaxonomiesController.php b/app/Http/Controllers/Api/Web/TaxonomiesController.php index 8085f3e9..a2107bf7 100644 --- a/app/Http/Controllers/Api/Web/TaxonomiesController.php +++ b/app/Http/Controllers/Api/Web/TaxonomiesController.php @@ -36,7 +36,9 @@ class TaxonomiesController extends ApiControllerBase '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(), + 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')) + ->where('id', '!=', TrackType::UNCLASSIFIED_TRACK) + ->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); diff --git a/app/Track.php b/app/Track.php index 0a25d422..d61f0321 100644 --- a/app/Track.php +++ b/app/Track.php @@ -24,6 +24,7 @@ use Auth; use Cache; use Config; use DB; +use Poniverse\Ponyfm\Traits\SlugTrait; use Exception; use External; use getid3_writetags; @@ -32,7 +33,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; use Log; -use Poniverse\Ponyfm\Traits\SlugTrait; use URL; class Track extends Model @@ -155,6 +155,16 @@ class Track extends Model ]); } + /** + * Limits results to MLP Music Archive tracks. + * + * @param $query + */ + public function scopeMlpma($query) + { + $query->join('mlpma_tracks', 'tracks.id', '=', 'mlpma_tracks.track_id'); + } + public static function popular($count, $allowExplicit = false) { $trackIds = Cache::remember('popular_tracks' . $count . '-' . ($allowExplicit ? 'explicit' : 'safe'), 5, diff --git a/app/TrackType.php b/app/TrackType.php index 59edd27a..23589e63 100644 --- a/app/TrackType.php +++ b/app/TrackType.php @@ -31,4 +31,5 @@ class TrackType extends Model const FAN_TRACK_REMIX = 3; const PONIFIED_TRACK = 4; const OFFICIAL_AUDIO_REMIX = 5; + const UNCLASSIFIED_TRACK = 6; } diff --git a/database/migrations/2015_10_26_231224_AddTrackSourceColumn.php b/database/migrations/2015_10_26_231224_AddTrackSourceColumn.php new file mode 100644 index 00000000..dfa4ec01 --- /dev/null +++ b/database/migrations/2015_10_26_231224_AddTrackSourceColumn.php @@ -0,0 +1,57 @@ +. + */ + +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; + +class AddTrackSourceColumn extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('tracks', function(Blueprint $table){ + $table->string('source', 40)->default('direct_upload'); + }); + + // Mark MLPMA tracks retroactively + // --> The default value in the database, set above, will + // be used automatically for all non-MLPMA tracks. + $tracks = DB::table('tracks') + ->join('mlpma_tracks', 'mlpma_tracks.track_id', '=', 'tracks.id'); + + $tracks->whereNotNull('mlpma_tracks.id')->update(['source' => 'mlpma']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tracks', function(Blueprint $table){ + $table->dropColumn('source'); + }); + } +} diff --git a/database/migrations/2015_10_28_162655_AddTrackFilesForDeletedTracks.php b/database/migrations/2015_10_28_162655_AddTrackFilesForDeletedTracks.php new file mode 100644 index 00000000..0763ef03 --- /dev/null +++ b/database/migrations/2015_10_28_162655_AddTrackFilesForDeletedTracks.php @@ -0,0 +1,68 @@ +. + */ + +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; +use Poniverse\Ponyfm\Track; + +class AddTrackFilesForDeletedTracks extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + // 2015_05_25_011121_create_track_files_table.php only created + // track_files records for non-deleted tracks. This migration + // adds them for deleted tracks, too. + + $tracks = Track::with('trackFiles') + ->onlyTrashed() + ->get(); + + foreach ($tracks as $track) { + if ($track->trackFiles->count() === 0 && $track->source !== 'mlpma') { + foreach (Track::$Formats as $name => $item) { + DB::table('track_files')->insert( + [ + 'track_id' => $track->id, + 'is_master' => $name === 'FLAC' ? true : false, + 'format' => $name, + 'created_at' => $track->created_at, + 'updated_at' => Carbon\Carbon::now() + ] + ); + } + } + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // There's no need to undo this one! + } +} diff --git a/database/migrations/2015_11_05_004145_AddUnclassifiedTrackType.php b/database/migrations/2015_11_05_004145_AddUnclassifiedTrackType.php new file mode 100644 index 00000000..52faafc7 --- /dev/null +++ b/database/migrations/2015_11_05_004145_AddUnclassifiedTrackType.php @@ -0,0 +1,49 @@ +. + */ + +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; + +class AddUnclassifiedTrackType extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + DB::table('track_types')->insert([ + 'id' => 6, + 'title' => 'Unclassified', + 'editor_title' => 'an unclassified track' + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('track_types')->where('id', 6)->delete(); + } +} diff --git a/public/templates/tracks/index.html b/public/templates/tracks/index.html index 9f4dc91c..6b5f8433 100644 --- a/public/templates/tracks/index.html +++ b/public/templates/tracks/index.html @@ -66,6 +66,12 @@ {{page}}
  • Next
  • +
  • + Jump… +
    + +
    +
  • diff --git a/resources/assets/scripts/app/controllers/tracks.coffee b/resources/assets/scripts/app/controllers/tracks.coffee index 9b34b153..33dbc0d4 100644 --- a/resources/assets/scripts/app/controllers/tracks.coffee +++ b/resources/assets/scripts/app/controllers/tracks.coffee @@ -21,8 +21,8 @@ window.pfm.preloaders['tracks'] = [ ] angular.module('ponyfm').controller "tracks", [ - '$scope', 'tracks', '$state' - ($scope, tracks, $state) -> + '$scope', 'tracks', '$state', 'focus' + ($scope, tracks, $state, focus) -> $scope.recentTracks = null $scope.query = tracks.mainQuery $scope.filters = tracks.filters @@ -52,10 +52,40 @@ angular.module('ponyfm').controller "tracks", [ $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.allPages = [1..$scope.totalPages] + + # TODO: turn this into a directive + # The actual first page will always be in the paginator. + $scope.pages = [1] + + # This logic determines how many pages to add prior to the current page, if any. + firstPage = Math.max(2, $scope.currentPage-3) + $scope.pages = $scope.pages.concat [firstPage..$scope.currentPage] unless $scope.currentPage == 1 + + pagesLeftToAdd = 8-$scope.pages.length + + lastPage = Math.min($scope.totalPages - 1, $scope.currentPage+1+pagesLeftToAdd) + $scope.pages = $scope.pages.concat([$scope.currentPage+1..lastPage]) unless $scope.currentPage >= lastPage + + # The actual last page will always be in the paginator. + $scope.pages.push($scope.totalPages) unless $scope.totalPages in $scope.pages + + $scope.pageSelectorShown = false + $scope.inputPageNumber = $scope.currentPage $scope.gotoPage = (page) -> $state.transitionTo 'content.tracks.list', {filter: $state.params.filter, page: page} + $scope.showPageSelector = () -> + $scope.pageSelectorShown = true + focus('#pagination-jump-destination') + + $scope.hidePageSelector = () -> + $scope.pageSelectorShown = false + + + $scope.jumpToPage = () -> + $scope.gotoPage($scope.inputPageNumber) + $scope.$on '$destroy', -> tracks.mainQuery = tracks.createQuery() ] diff --git a/resources/assets/scripts/app/services/focus.coffee b/resources/assets/scripts/app/services/focus.coffee new file mode 100644 index 00000000..63033c38 --- /dev/null +++ b/resources/assets/scripts/app/services/focus.coffee @@ -0,0 +1,27 @@ +# Pony.fm - A community for pony fan music. +# Copyright (C) 2015 Peter Deltchev +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +# This service provides a way to set the browser's focus to a particular element +# using a jQuery selector. +# +# Based on: https://stackoverflow.com/a/25597540/3225811 +angular.module('ponyfm').factory('focus', ['$timeout', '$window', ($timeout, $window) -> + (selector) -> + $timeout () -> + element = $window.jQuery("#{selector}") + element.focus() # will do nothing if the selector doesn't select anything +]) diff --git a/resources/assets/styles/components.less b/resources/assets/styles/components.less index 4089346b..e9a36941 100644 --- a/resources/assets/styles/components.less +++ b/resources/assets/styles/components.less @@ -408,4 +408,18 @@ html { float: right; } } + + .pagination-jump { + form { + margin: 0; + } + + input[type="number"] { + margin: 0; + height: 22px; + width: 60px; + padding: 0 5px; + text-align: center; + } + } } diff --git a/resources/assets/styles/forms.less b/resources/assets/styles/forms.less index 117835ef..fd78f0b1 100644 --- a/resources/assets/styles/forms.less +++ b/resources/assets/styles/forms.less @@ -51,13 +51,13 @@ label.strong { font-weight: bold; } -input[type="text"], input[type="password"], input[type="date"], textarea { +input[type="text"], input[type="password"], input[type="date"], input[type="number"], textarea { padding: 3px; border: 1px solid; border-color: #9c9c9c #9c9c9c #ccc #ccc; } -input[type="text"], input[type="password"], input[type="date"], textarea, select { +input[type="text"], input[type="password"], input[type="date"], input[type="number"], textarea, select { .border-radius(0px); .box-sizing(border-box); diff --git a/resources/views/shared/_app_layout.blade.php b/resources/views/shared/_app_layout.blade.php index 0de0363e..2f6ced70 100644 --- a/resources/views/shared/_app_layout.blade.php +++ b/resources/views/shared/_app_layout.blade.php @@ -99,7 +99,7 @@
  • Register
  • @endif
  • - + @if(config('ponyfm.use_powered_by_footer')) Powered by Pony.fm logo