diff --git a/app/controllers/Api/Web/AlbumsController.php b/app/controllers/Api/Web/AlbumsController.php index c03ef96a..903529f2 100644 --- a/app/controllers/Api/Web/AlbumsController.php +++ b/app/controllers/Api/Web/AlbumsController.php @@ -2,9 +2,9 @@ namespace Api\Web; + use Commands\CreateAlbumCommand; use Commands\DeleteTrackCommand; use Commands\EditTrackCommand; - use Commands\UploadTrackCommand; use Cover; use Entities\Album; use Entities\Image; @@ -15,44 +15,42 @@ class AlbumsController extends \ApiControllerBase { public function getOwned() { - $query = Album::summary()->where('user_id', \Auth::user()->id); - return Response::json($query->get(), 200); + $query = Album::summary()->where('user_id', \Auth::user()->id)->get(); + $albums = []; + foreach ($query as $album) { + $albums[] = [ + 'id' => $album->id, + 'title' => $album->title, + 'slug' => $album->slug, + 'created_at' => $album->created_at, + 'cover_url' => $album->getCoverUrl(Image::SMALL) + ]; + } + return Response::json($albums, 200); + } + + public function postCreate() { + return $this->execute(new CreateAlbumCommand(Input::all())); } public function getEdit($id) { - $track = Track::with('showSongs')->find($id); - if (!$track) - return $this->notFound('Track ' . $id . ' not found!'); + $album = Album::find($id); + if (!$album) + return $this->notFound('Album ' . $id . ' not found!'); - if ($track->user_id != Auth::user()->id) + if ($album->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 + 'id' => $album->id, + 'title' => $album->title, + 'user_id' => $album->user_id, + 'slug' => $album->slug, + 'created_at' => $album->created_at, + 'published_at' => $album->published_at, + 'description' => $album->description, + 'cover_url' => $album->hasCover() ? $album->getCoverUrl(Image::NORMAL) : null, + 'real_cover_url' => $album->getCoverUrl(Image::NORMAL) ], 200); } diff --git a/app/controllers/Api/Web/ImagesController.php b/app/controllers/Api/Web/ImagesController.php index 019bbee5..6a0048fe 100644 --- a/app/controllers/Api/Web/ImagesController.php +++ b/app/controllers/Api/Web/ImagesController.php @@ -19,8 +19,12 @@ foreach ($query->get() as $image) { $images[] = [ 'id' => $image->id, - 'url' => $image->getUrl(Image::SMALL), - 'url_normal' => $image->getUrl(Image::NORMAL), + 'urls' => [ + 'small' => $image->getUrl(Image::SMALL), + 'normal' => $image->getUrl(Image::NORMAL), + 'thumbnail' => $image->getUrl(Image::THUMBNAIL), + 'original' => $image->getUrl(Image::ORIGINAL) + ], 'filename' => $image->filename ]; } diff --git a/app/models/Commands/CreateAlbumCommand.php b/app/models/Commands/CreateAlbumCommand.php new file mode 100644 index 00000000..a8a6d701 --- /dev/null +++ b/app/models/Commands/CreateAlbumCommand.php @@ -0,0 +1,62 @@ +_input = $input; + } + + /** + * @return bool + */ + public function authorize() { + $user = \Auth::user(); + return $user != null; + } + + /** + * @throws \Exception + * @return CommandResponse + */ + public function execute() { + $rules = [ + 'title' => 'required|min:3|max:50', + 'description' => '', + 'cover' => 'image|mimes:png|min_width:350|min_height:350', + 'cover_id' => 'exists:images,id', + ]; + + $validator = \Validator::make($this->_input, $rules); + + if ($validator->fails()) + return CommandResponse::fail($validator); + + $album = new Album(); + $album->user_id = Auth::user()->id; + $album->title = $this->_input['title']; + $album->description = $this->_input['description']; + + if (isset($this->_input['cover_id'])) { + $album->cover_id = $this->_input['cover_id']; + } + else if (isset($this->_input['cover'])) { + $cover = $this->_input['cover']; + $album->cover_id = Image::upload($cover, Auth::user())->id; + } else if ($this->_input['remove_cover'] == 'true') + $album->cover_id = null; + + $album->save(); + + return CommandResponse::succeed(['id' => $album->id]); + } + } \ No newline at end of file diff --git a/app/models/Entities/Album.php b/app/models/Entities/Album.php index 238e2bb1..a281f6b0 100644 --- a/app/models/Entities/Album.php +++ b/app/models/Entities/Album.php @@ -9,7 +9,7 @@ protected $softDelete = true; public static function summary() { - return self::select('id', 'title', 'user_id', 'slug', 'created_at'); + return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'cover_id'); } protected $table = 'albums'; diff --git a/app/routes.php b/app/routes.php index 2155420c..71db8ee4 100644 --- a/app/routes.php +++ b/app/routes.php @@ -36,6 +36,8 @@ Route::post('/tracks/upload', 'Api\Web\TracksController@postUpload'); Route::post('/tracks/delete/{id}', 'Api\Web\TracksController@postDelete'); Route::post('/tracks/edit/{id}', 'Api\Web\TracksController@putEdit'); + + Route::post('/albums/create', 'Api\Web\AlbumsController@postCreate'); }); Route::group(['before' => 'auth'], function() { diff --git a/public/scripts/app/controllers/account-albums-edit.coffee b/public/scripts/app/controllers/account-albums-edit.coffee index 7afb28df..a3e0cf30 100644 --- a/public/scripts/app/controllers/account-albums-edit.coffee +++ b/public/scripts/app/controllers/account-albums-edit.coffee @@ -1,17 +1,87 @@ angular.module('ponyfm').controller "account-albums-edit", [ '$scope', '$state', 'taxonomies', '$dialog', 'lightbox' ($scope, $state, taxonomies, $dialog, lightbox) -> - $scope.isNew = $state.params.album_id == null + $scope.isNew = $state.params.album_id == undefined $scope.data.isEditorOpen = true $scope.errors = {} $scope.isDirty = false + $scope.album = {} + $scope.isSaving = false $scope.touchModel = -> $scope.isDirty = true + $scope.refresh = () -> + return if $scope.isNew + $.getJSON('/api/web/albums/edit/' + $scope.data.selectedAlbum.id) + .done (album) -> $scope.$apply -> + $scope.isDirty = false + $scope.errors = {} + $scope.album = + id: album.id + title: album.title + description: album.description + remove_cover: false + cover: album.cover_url + if $scope.isNew $scope.album = title: '' description: '' + else + $scope.refresh(); $scope.$on '$destroy', -> $scope.data.isEditorOpen = false + + $scope.saveAlbum = -> + url = + if $scope.isNew + '/api/web/albums/create' + else + '/api/web/albums/edit' + $scope.album.id + + xhr = new XMLHttpRequest() + xhr.onload = -> $scope.$apply -> + $scope.isSaving = false + response = $.parseJSON(xhr.responseText).errors + if xhr.status != 200 + $scope.errors = {} + _.each response.errors, (value, key) -> $scope.errors[key] = value.join ', ' + return + + $scope.$emit 'album-updated' + + if $scope.isNew + $state.transitionTo 'account-content.albums.edit', {album_id: response.id} + else + $scope.refresh() + + formData = new FormData() + + _.each $scope.album, (value, name) -> + if name == 'cover' + return if value == null + if typeof(value) == 'object' + formData.append name, value, value.name + else + formData.append name, value + + xhr.open 'POST', url, true + xhr.setRequestHeader 'X-Token', pfm.token + $scope.isSaving = true + xhr.send formData + + $scope.deleteAlbum = -> + + $scope.setCover = (image, type) -> + delete $scope.album.cover_id + delete $scope.album.cover + + if image == null + $scope.album.remove_cover = true + else if type == 'file' + $scope.album.cover = image + else if type == 'gallery' + $scope.album.cover_id = image.id + + $scope.isDirty = true ] \ No newline at end of file diff --git a/public/scripts/app/controllers/account-albums.coffee b/public/scripts/app/controllers/account-albums.coffee index e338fbcf..87bfbcf7 100644 --- a/public/scripts/app/controllers/account-albums.coffee +++ b/public/scripts/app/controllers/account-albums.coffee @@ -1,19 +1,28 @@ angular.module('ponyfm').controller "account-albums", [ '$scope', '$state', 'taxonomies', '$dialog', 'lightbox' ($scope, $state, taxonomies, $dialog, lightbox) -> - refreshList = () -> - $.getJSON('/api/web/albums/owned') - .done (albums) -> - $scope.albums = albums + albumsDb = {} - refreshList() + $scope.albums = [] $scope.data = isEditorOpen: false selectedAlbum: null + refreshList = () -> + $.getJSON('/api/web/albums/owned') + .done (albums) -> $scope.$apply -> + albumsDb[album.id] = album for album in albums + $scope.albums = albums + + selectAlbum albumsDb[$state.params.album_id] if $state.params.album_id != undefined + + selectAlbum = (album) -> $scope.data.selectedAlbum = album + $scope.$on '$stateChangeSuccess', () -> if $state.params.album_id selectAlbum albumsDb[$state.params.album_id] else selectAlbum null + + refreshList() ] \ No newline at end of file diff --git a/public/scripts/app/controllers/account-tracks.coffee b/public/scripts/app/controllers/account-tracks.coffee index 9d37b13a..df4e0e28 100644 --- a/public/scripts/app/controllers/account-tracks.coffee +++ b/public/scripts/app/controllers/account-tracks.coffee @@ -1,11 +1,6 @@ angular.module('ponyfm').controller "account-tracks", [ '$scope', '$state', 'taxonomies', '$dialog', 'lightbox' ($scope, $state, taxonomies, $dialog, lightbox) -> - $('#coverPreview').load () -> - $scope.$apply -> $scope.isCoverLoaded = true - window.alignVertically(this) - - $scope.isCoverLoaded = false $scope.selectedTrack = null $scope.isDirty = false $scope.isSaving = false @@ -13,6 +8,19 @@ angular.module('ponyfm').controller "account-tracks", [ $scope.selectedSongsTitle = 'None' $scope.selectedSongs = {} + $scope.setCover = (image, type) -> + delete $scope.edit.cover_id + delete $scope.edit.cover + + if image == null + $scope.edit.remove_cover = true + else if type == 'file' + $scope.edit.cover = image + else if type == 'gallery' + $scope.edit.cover_id = image.id + + $scope.isDirty = true + updateSongDisplay = () -> if _.size $scope.selectedSongs $scope.selectedSongsTitle = (_.map _.values($scope.selectedSongs), (s) -> s.title).join(', ') @@ -31,25 +39,6 @@ angular.module('ponyfm').controller "account-tracks", [ $scope.updateIsVocal = () -> delete $scope.errors.lyrics if !$scope.edit.is_vocal - $scope.previewCover = () -> - return if !$scope.edit.cover && !$scope.edit.cover_id - - if $scope.edit.cover_id - lightbox.openImageUrl $scope.cover_url - else - if typeof($scope.edit.cover) == 'object' - lightbox.openDataUrl $('#coverPreview').attr 'src' - else - lightbox.openImageUrl $scope.edit.cover - - $scope.selectGalleryImage = (image) -> - $('#coverPreview').attr 'src', image.url - $scope.edit.cover_id = image.id - $scope.cover_url = image.url_normal - $scope.edit.remove_cover = false - $scope.edit.cover = null - $scope.isDirty = true - $scope.updateTrack = (track) -> xhr = new XMLHttpRequest() xhr.onload = -> $scope.$apply -> @@ -85,35 +74,6 @@ angular.module('ponyfm').controller "account-tracks", [ $scope.isSaving = true xhr.send formData - $scope.uploadTrackCover = () -> - $("#coverImage").trigger 'click' - - $scope.setCoverImage = (input) -> - $scope.$apply -> - delete $scope.edit.cover_id - previewElement = $('#coverPreview')[0] - file = input.files[0] - - if file.type != 'image/png' - $scope.errors.cover = 'Cover image must be a png!' - $scope.isCoverLoaded = false - $scope.edit.cover = null - return - - delete $scope.errors.cover - $scope.isDirty = true - reader = new FileReader() - reader.onload = (e) -> previewElement.src = e.target.result - reader.readAsDataURL file - $scope.edit.cover = file - - $scope.clearTrackCover = () -> - $scope.isCoverLoaded = false - $scope.isDirty = true - $scope.edit.remove_cover = true - delete $scope.edit.cover_id - delete $scope.edit.cover - $scope.filters = published: [ {title: 'Either', query: ''}, @@ -185,7 +145,6 @@ angular.module('ponyfm').controller "account-tracks", [ selectTrack = (t) -> $scope.selectedTrack = t - $scope.isCoverLoaded = false return if !t $.getJSON('/api/web/tracks/edit/' + t.id) .done (track) -> $scope.$apply -> @@ -214,10 +173,6 @@ angular.module('ponyfm').controller "account-tracks", [ trackDbItem.is_published = track.is_published trackDbItem.cover_url = track.real_cover_url - if track.cover_url - $('#coverPreview').attr 'src', track.cover_url - $scope.isCoverLoaded = true - $scope.selectedSongs = {} $scope.selectedSongs[song.id] = song for song in track.show_songs updateSongDisplay() diff --git a/public/scripts/app/directives/image-upload.coffee b/public/scripts/app/directives/image-upload.coffee index 139ecc59..7fbef992 100644 --- a/public/scripts/app/directives/image-upload.coffee +++ b/public/scripts/app/directives/image-upload.coffee @@ -1,9 +1,86 @@ -angular.module('ponyfm').directive 'pfmImageUpload', +angular.module('ponyfm').directive 'pfmImageUpload', () -> + $image = null + $uploader = null + restrict: 'E' + templateUrl: '/templates/directives/image-upload.html' scope: - setUploadedImage: '&' - setGalleryImage: '&' + setImage: '=setImage' + image: '=image' + + compile: (element) -> + $image = element.find 'img' + $uploader = element.find 'input' + controller: [ - 'upload' - (upload) -> (scope) -> + 'images', '$scope', 'lightbox' + (images, $scope, lightbox) -> + $scope.imageObject = null + $scope.imageFile = null + $scope.imageUrl = null + $scope.isImageLoaded = false + $scope.error = null + + $scope.$watch 'image', (val) -> + $scope.imageObject = $scope.imageFile = $scope.imageUrl = null + $scope.isImageLoaded = false + return if !val + + $scope.imageUrl = val + $image.attr 'src', val + $scope.isImageLoaded = true + + $image.load () -> $scope.$apply -> + $scope.isImageLoaded = true + window.setTimeout (() -> window.alignVertically($image)), 0 + + images.refresh().done (images) -> $scope.images = images + + $scope.previewImage = () -> + return if !$scope.isImageLoaded + + if $scope.imageObject + lightbox.openImageUrl $scope.imageObject.urls.normal + else if $scope.imageFile + lightbox.openDataUrl $image.attr 'src' + else if $scope.imageUrl + lightbox.openImageUrl $scope.imageUrl + + $scope.uploadImage = () -> + $uploader.trigger 'click' + + $scope.clearImage = () -> + $scope.imageObject = $scope.imageFile = $scope.imageUrl = null + $scope.isImageLoaded = false + $scope.setImage null + + $scope.selectGalleryImage = (image) -> + $scope.imageObject = image + $scope.imageFile = null + $scope.imageUrl = image.urls.small + $image.attr 'src', image.urls.small + $scope.isImageLoaded = true + $scope.setImage image, 'gallery' + + $scope.setImageFile = (input) -> + $scope.$apply -> + file = input.files[0] + $scope.imageObject = null + $scope.imageFile = file + + if file.type != 'image/png' + $scope.error = 'Image must be a png!' + $scope.isImageLoaded = false + $scope.imageObject = $scope.imageFile = $scope.imageUrl = null + return + + $scope.error = null + $scope.setImage file, 'file' + + reader = new FileReader() + reader.onload = (e) -> $scope.$apply -> + $image[0].src = e.target.result + $scope.isImageLoaded = true + + reader.readAsDataURL file ] \ No newline at end of file diff --git a/public/scripts/app/services/images.coffee b/public/scripts/app/services/images.coffee new file mode 100644 index 00000000..d961bd3b --- /dev/null +++ b/public/scripts/app/services/images.coffee @@ -0,0 +1,25 @@ +angular.module('ponyfm').factory('images', [ + '$rootScope' + ($rootScope) -> + def = null + self = + images: [] + isLoading: true + refresh: () -> + return def if def + def = new $.Deferred() + + self.images = [] + self.isLoading = true + + $.getJSON('/api/web/images/owned').done (images) -> $rootScope.$apply -> + self.images = images + self.isLoading = false + def.resolve images + + return def + + self.refresh() + return self +]) + diff --git a/public/styles/account-content.less b/public/styles/account-content.less index 681d7a40..4327a5fd 100644 --- a/public/styles/account-content.less +++ b/public/styles/account-content.less @@ -201,66 +201,15 @@ padding: 3px; font-size: 8pt; } - - &.has-error { - label { - color: @red; - } - - .error { - display: block; - } - } } - .cover-upload { - overflow: hidden; + .has-error { + label { + color: @red; + } .error { - clear: left; - margin-top: 14px; - } - - input[type=file] { - display: none; - } - - .btn { - .border-radius(0px); - } - - .preview { - .img-polaroid(); - overflow: hidden; - width: 46px; - height: 46px; - float: left; - - img { - position: relative; - display: block; - width: 100%; - } - - &.canOpen { - cursor: pointer; - - &:hover { - border-color: @blue; - border-style: solid; - } - } - } - - p, .btn-group { - color: #555; - margin-left: 60px; display: block; - margin-bottom: 1px; - } - - p { - font-size: 9pt; } } diff --git a/public/styles/components.less b/public/styles/components.less index 5c3d7690..0ee9d0ab 100644 --- a/public/styles/components.less +++ b/public/styles/components.less @@ -94,4 +94,55 @@ html body { &.open { display: block; } +} + +.image-upload { + overflow: hidden; + + .error { + clear: left; + margin-top: 14px; + } + + input[type=file] { + display: none; + } + + .btn { + .border-radius(0px); + } + + .preview { + .img-polaroid(); + overflow: hidden; + width: 46px; + height: 46px; + float: left; + + img { + position: relative; + display: block; + width: 100%; + } + + &.canOpen { + cursor: pointer; + + &:hover { + border-color: @blue; + border-style: solid; + } + } + } + + p, .btn-group { + color: #555; + margin-left: 60px; + display: block; + margin-bottom: 1px; + } + + p { + font-size: 9pt; + } } \ No newline at end of file diff --git a/public/templates/account/content/album.html b/public/templates/account/content/album.html index 2a146e6b..b2c3d653 100644 --- a/public/templates/account/content/album.html +++ b/public/templates/account/content/album.html @@ -1,4 +1,4 @@ -