Image things

This commit is contained in:
nelsonlaquet 2013-07-26 22:00:45 -05:00
parent c56568b6f5
commit 3b291f3b8f
12 changed files with 210 additions and 22 deletions

View file

@ -0,0 +1,29 @@
<?php
namespace Api\Web;
use Commands\DeleteTrackCommand;
use Commands\EditTrackCommand;
use Commands\UploadTrackCommand;
use Cover;
use Entities\Image;
use Entities\Track;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
class ImagesController extends \ApiControllerBase {
public function getOwned() {
$query = Image::where('uploaded_by', \Auth::user()->id);
$images = [];
foreach ($query->get() as $image) {
$images[] = [
'id' => $image->id,
'url' => $image->getUrl(Image::SMALL),
'filename' => $image->filename
];
}
return Response::json($images, 200);
}
}

View file

@ -92,7 +92,8 @@
'description' => $track->description, 'description' => $track->description,
'lyrics' => $track->lyrics, 'lyrics' => $track->lyrics,
'released_at' => $track->released_at, 'released_at' => $track->released_at,
'cover_url' => $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null 'cover_url' => $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null,
'real_cover_url' => $track->getCoverUrl(Image::NORMAL)
], 200); ], 200);
} }

View file

@ -15,6 +15,17 @@
if (!$image) if (!$image)
App::abort(404); App::abort(404);
return File::inline($image->getFile($coverType['id']), $image->mime, $image->filename); $filename = $image->getFile($coverType['id']);
$lastModified = filemtime($filename);
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $lastModified == $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
header('HTTP/1.0 304 Not Modified');
exit();
}
header('Last-Modified: ' . $lastModified);
header('Cache-Control: max-age=' . (60 * 60 * 24 * 7));
return File::inline($filename, $image->mime, $image->filename);
} }
} }

View file

@ -42,7 +42,8 @@
'genre_id' => 'required|exists:genres,id', 'genre_id' => 'required|exists:genres,id',
'cover' => 'image|mimes:png|min_width:350|min_height:350', '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',
'songs' => 'required_when:track_type,2|exists:songs,id' 'songs' => 'required_when:track_type,2|exists:songs,id',
'cover_id' => 'exists:images,id'
]); ]);
if ($validator->fails()) if ($validator->fails())
@ -64,10 +65,13 @@
$track->published_at = new \DateTime(); $track->published_at = new \DateTime();
} }
if (isset($this->_input['cover'])) { if (isset($this->_input['cover_id'])) {
$track->cover_id = $this->_input['cover_id'];
}
else if (isset($this->_input['cover'])) {
$cover = $this->_input['cover']; $cover = $this->_input['cover'];
$track->cover_id = Image::Upload($cover, Auth::user())->id; $track->cover_id = Image::Upload($cover, Auth::user())->id;
} else } else if ($this->_input['remove_cover'] == 'true')
$track->cover_id = null; $track->cover_id = null;
$track->save(); $track->save();

View file

@ -39,6 +39,7 @@
}); });
Route::group(['before' => 'auth'], function() { Route::group(['before' => 'auth'], function() {
Route::get('/images/owned', 'Api\Web\ImagesController@getOwned');
Route::get('/tracks/owned', 'Api\Web\TracksController@getOwned'); Route::get('/tracks/owned', 'Api\Web\TracksController@getOwned');
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit'); Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
}); });

View file

@ -8,6 +8,7 @@ angular.module('ponyfm').controller "account-content-tracks", [
$scope.isCoverLoaded = false $scope.isCoverLoaded = false
$scope.selectedTrack = null $scope.selectedTrack = null
$scope.isDirty = false $scope.isDirty = false
$scope.isSaving = false
$scope.taxonomies = $scope.taxonomies =
trackTypes: taxonomies.trackTypes trackTypes: taxonomies.trackTypes
licenses: taxonomies.licenses licenses: taxonomies.licenses
@ -18,11 +19,22 @@ angular.module('ponyfm').controller "account-content-tracks", [
$scope.previewCover = () -> $scope.previewCover = () ->
return if !$scope.edit.cover return if !$scope.edit.cover
if typeof($scope.edit.cover) == 'object'
lightbox.openDataUrl $('#coverPreview').attr 'src' 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.edit.remove_cover = false
$scope.edit.cover = null
$scope.isDirty = true
$scope.updateTrack = (track) -> $scope.updateTrack = (track) ->
xhr = new XMLHttpRequest() xhr = new XMLHttpRequest()
xhr.onload = -> $scope.$apply -> xhr.onload = -> $scope.$apply ->
$scope.isSaving = false
if xhr.status != 200 if xhr.status != 200
errors = errors =
if xhr.getResponseHeader('content-type') == 'application/json' if xhr.getResponseHeader('content-type') == 'application/json'
@ -40,12 +52,15 @@ angular.module('ponyfm').controller "account-content-tracks", [
formData = new FormData(); formData = new FormData();
_.each $scope.edit, (value, name) -> _.each $scope.edit, (value, name) ->
if name == 'cover' if name == 'cover'
return if value == null
if typeof(value) == 'object'
formData.append name, value, value.name formData.append name, value, value.name
else else
formData.append name, value formData.append name, value
xhr.open 'POST', '/api/web/tracks/edit/' + $scope.edit.id, true xhr.open 'POST', '/api/web/tracks/edit/' + $scope.edit.id, true
xhr.setRequestHeader 'X-Token', pfm.token xhr.setRequestHeader 'X-Token', pfm.token
$scope.isSaving = true
xhr.send formData xhr.send formData
$scope.uploadTrackCover = () -> $scope.uploadTrackCover = () ->
@ -53,6 +68,7 @@ angular.module('ponyfm').controller "account-content-tracks", [
$scope.setCoverImage = (input) -> $scope.setCoverImage = (input) ->
$scope.$apply -> $scope.$apply ->
delete $scope.edit.cover_id
previewElement = $('#coverPreview')[0] previewElement = $('#coverPreview')[0]
file = input.files[0] file = input.files[0]
@ -71,6 +87,9 @@ angular.module('ponyfm').controller "account-content-tracks", [
$scope.clearTrackCover = () -> $scope.clearTrackCover = () ->
$scope.isCoverLoaded = false $scope.isCoverLoaded = false
$scope.isDirty = true
$scope.edit.remove_cover = true
delete $scope.edit.cover_id
delete $scope.edit.cover delete $scope.edit.cover
$scope.filters = $scope.filters =
@ -162,6 +181,20 @@ angular.module('ponyfm').controller "account-content-tracks", [
genre_id: track.genre_id genre_id: track.genre_id
track_type_id: track.track_type_id track_type_id: track.track_type_id
released_at: if track.released_at then track.released_at.date else '' released_at: if track.released_at then track.released_at.date else ''
remove_cover: false
cover: track.cover_url
trackDbItem = tracksDb[t.id]
trackDbItem.title = track.title
trackDbItem.is_explicit = track.is_explicit
trackDbItem.is_vocal = track.is_vocal
trackDbItem.genre_id = track.genre_id
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.touchModel = -> $scope.isDirty = true $scope.touchModel = -> $scope.isDirty = true

View file

@ -0,0 +1,10 @@
angular.module('ponyfm').controller "account-image-select", [
'$scope'
($scope) ->
$scope.images = []
$scope.isLoading = true
$.getJSON('/api/web/images/owned').done (images) -> $scope.$apply ->
$scope.images = images
$scope.isLoading = false
]

View file

@ -0,0 +1,36 @@
angular.module('ponyfm').directive 'pfmPopup', () ->
(scope, element, attrs) ->
$popup = $ '#' + attrs.pfmPopup
$element = $ element
$popup.remove()
open = false
documentClickHandler = () ->
return if !open
$popup.removeClass 'open'
open = false
$(document.body).bind 'click', documentClickHandler
$(document.body).append $popup
$(element).click (e) ->
e.preventDefault()
e.stopPropagation()
if open
open = false
$popup.removeClass 'open'
return
position = $element.offset()
$popup.addClass 'open'
$popup.css
top: position.top + $element.height() + 10
left: position.left
open = true
scope.$on '$destroy', () ->
$(document.body).unbind 'click', documentClickHandler
$popup.remove()

View file

@ -4,5 +4,10 @@ angular.module('ponyfm').factory('lightbox', [
$.colorbox $.colorbox
html: '<img src="' + src + '" />' html: '<img src="' + src + '" />'
transition: 'none' transition: 'none'
openImageUrl: (src) ->
$.colorbox
href: src
transition: 'none'
]) ])

View file

@ -15,6 +15,38 @@
} }
} }
.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;
}
}
}
}
}
.account-tracks-listing { .account-tracks-listing {
overflow-y: auto; overflow-y: auto;
margin: 0px; margin: 0px;

View file

@ -81,3 +81,15 @@ html body {
} }
} }
} }
.pfm-popup {
.box-shadow(0 5px 10px rgba(0, 0, 0, 0.2));
position: absolute;
display: none;
border: 1px solid rgba(0, 0, 0, 0.2);
background: #fff;
&.open {
display: block;
}
}

View file

@ -58,42 +58,48 @@
<form novalidate ng-submit="updateTrack(edit)"> <form novalidate ng-submit="updateTrack(edit)">
<ul class="toolbar"> <ul class="toolbar">
<li> <li>
<button type="submit" class="btn" ng-class="{disabled: selectedTrack.is_published && !isDirty, 'btn-primary': !selectedTrack.is_published || isDirty}"> <button type="submit" class="btn" ng-class="{disabled: (selectedTrack.is_published && !isDirty) || isSaving, 'btn-primary': !selectedTrack.is_published || isDirty}">
<span ng-show="selectedTrack.is_published">Save Changes</span> <span ng-show="selectedTrack.is_published">
<span ng-hide="selectedTrack.is_published">Publish Track</span> Save Changes
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
</span>
<span ng-hide="selectedTrack.is_published">
Publish Track
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
</span>
</button> </button>
</li> </li>
<li class="delete"><a class="btn btn-danger" href="#" ng-click="deleteTrack(selectedTrack)" pfm-eat-click>Delete Track</a></li> <li class="delete"><a ng-class="{disabled: isSaving}" class="btn btn-danger" href="#" ng-click="deleteTrack(selectedTrack)" pfm-eat-click>Delete Track</a></li>
</ul> </ul>
<div class="strech-to-bottom"> <div class="strech-to-bottom">
<div class="form-row" ng-class="{'has-error': errors.title != null}"> <div class="form-row" ng-class="{'has-error': errors.title != null}">
<label for="title" class="strong">Title:</label> <label for="title" class="strong">Title:</label>
<input required ng-change="touchModel()" placeholder="Track Title" type="text" id="title" ng-model="edit.title" /> <input ng-disabled="isSaving" ng-change="touchModel()" placeholder="Track Title" type="text" id="title" ng-model="edit.title" />
<div class="error">{{errors.title}}</div> <div class="error">{{errors.title}}</div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="span6 form-row" ng-class="{'has-error': errors.description != null}"> <div class="span6 form-row" ng-class="{'has-error': errors.description != null}">
<label for="description" class="strong">Description:</label> <label for="description" class="strong">Description:</label>
<textarea ng-change="touchModel()" placeholder="Description (optional)" id="description" ng-model="edit.description"></textarea> <textarea ng-disabled="isSaving" ng-change="touchModel()" placeholder="Description (optional)" id="description" ng-model="edit.description"></textarea>
<div class="error">{{errors.description}}</div> <div class="error">{{errors.description}}</div>
</div> </div>
<div class="span6 form-row" ng-class="{'has-error': errors.lyrics != null}"> <div class="span6 form-row" ng-class="{'has-error': errors.lyrics != null}">
<label for="is_vocal" class="strong"><input ng-change="touchModel(); updateIsVocal()" id="is_vocal" type="checkbox" ng-model="edit.is_vocal" /> Is Vocal</label> <label for="is_vocal" class="strong"><input ng-disabled="isSaving" ng-change="touchModel(); updateIsVocal()" id="is_vocal" type="checkbox" ng-model="edit.is_vocal" /> Is Vocal</label>
<textarea ng-change="touchModel()" ng-show="edit.is_vocal" ng-animate="'fade'" placeholder="Lyrics (required)" id="lyrics" ng-model="edit.lyrics"></textarea> <textarea ng-disabled="isSaving" ng-change="touchModel()" ng-show="edit.is_vocal" ng-animate="'fade'" placeholder="Lyrics (required)" id="lyrics" ng-model="edit.lyrics"></textarea>
<div class="error">{{errors.lyrics}}</div> <div class="error">{{errors.lyrics}}</div>
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.track_type_id != null}"> <div class="form-row span6" ng-class="{'has-error': errors.track_type_id != null}">
<label for="track_type" class="strong">This track is...</label> <label for="track_type" class="strong">This track is...</label>
<select required id="track_type" ng-change="touchModel()" ng-model="edit.track_type_id" ng-options="type.id as type.editor_title for type in taxonomies.trackTypes"> <select ng-disabled="isSaving" id="track_type" ng-change="touchModel()" ng-model="edit.track_type_id" ng-options="type.id as type.editor_title for type in taxonomies.trackTypes">
<option value="">Please select a type...</option> <option value="">Please select a type...</option>
</select> </select>
<div class="error">{{errors.track_type_id}}</div> <div class="error">{{errors.track_type_id}}</div>
</div> </div>
<div class="form-row span6" ng-class="{'has-error': errors.genre_id != null}"> <div class="form-row span6" ng-class="{'has-error': errors.genre_id != null}">
<label for="genre" class="strong">Genre:</label> <label for="genre" class="strong">Genre:</label>
<select required id="genre" ng-change="touchModel()" ng-model="edit.genre_id" ng-options="genre.id as genre.name for genre in taxonomies.genres"> <select ng-disabled="isSaving" id="genre" ng-change="touchModel()" ng-model="edit.genre_id" ng-options="genre.id as genre.name for genre in taxonomies.genres">
<option value="">Please select a genre...</option> <option value="">Please select a genre...</option>
</select> </select>
<div class="error">{{errors.genre_id}}</div> <div class="error">{{errors.genre_id}}</div>
@ -109,24 +115,32 @@
<input type="file" id="coverImage" onchange="angular.element(this).scope().setCoverImage(this)" /> <input type="file" id="coverImage" onchange="angular.element(this).scope().setCoverImage(this)" />
</p> </p>
<div class="btn-group"> <div class="btn-group">
<a href="#" pfm-popup="image-selector" class="btn btn-small"><i class="icon-picture"></i> Gallery</a>
<a href="#" pfm-eat-click ng-click="uploadTrackCover()" class="btn btn-info btn-small"><i class="icon-upload"></i> Upload</a> <a href="#" pfm-eat-click ng-click="uploadTrackCover()" class="btn btn-info btn-small"><i class="icon-upload"></i> Upload</a>
<a href="#" pfm-eat-click ng-click="clearTrackCover()" class="btn btn-danger btn-small" ng-show="edit.cover"><i class="icon-remove"></i></a> <a href="#" pfm-eat-click ng-click="clearTrackCover()" class="btn btn-danger btn-small" ng-show="edit.cover || edit.cover_id"><i class="icon-remove"></i></a>
</div>
<div id="image-selector" class="pfm-popup image-selector" ng-controller="account-image-select">
<ul>
<li ng-repeat="image in images" ng-click="selectGalleryImage(image)">
<img src="{{image.url}}" />
</li>
</ul>
</div> </div>
<div class="error">{{errors.cover}}</div> <div class="error">{{errors.cover}}</div>
</div> </div>
</div> </div>
<div class="form-row span6" ng-class="{'has-error': errors.released_at != null}"> <div class="form-row span6" ng-class="{'has-error': errors.released_at != null}">
<label for="released_at" class="strong">Release Date:</label> <label for="released_at" class="strong">Release Date:</label>
<input type="text" id="released_at" ui-date ng-model="edit.released_at" ng-change="touchModel()" ui-date-format="yy-mm-dd" /> <input ng-disabled="isSaving" type="text" id="released_at" ui-date ng-model="edit.released_at" ng-change="touchModel()" ui-date-format="yy-mm-dd" />
<div class="error">{{errors.released_at}}</div> <div class="error">{{errors.released_at}}</div>
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="span6 form-row"> <div class="span6 form-row">
<label for="is_explicit"><input ng-change="touchModel()" id="is_explicit" type="checkbox" ng-model="edit.is_explicit" /> Contains Explicit Content</label> <label for="is_explicit"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_explicit" type="checkbox" ng-model="edit.is_explicit" /> Contains Explicit Content</label>
</div> </div>
<div class="span6 form-row"> <div class="span6 form-row">
<label for="is_downloadable"><input ng-change="touchModel()" id="is_downloadable" type="checkbox" ng-model="edit.is_downloadable" /> Is Downloadable</label> <label for="is_downloadable"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_downloadable" type="checkbox" ng-model="edit.is_downloadable" /> Is Downloadable</label>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -136,7 +150,7 @@
<div ng-click="edit.license_id = license.id; touchModel()"> <div ng-click="edit.license_id = license.id; touchModel()">
<strong>{{license.title}}</strong> <strong>{{license.title}}</strong>
<p>{{license.description}}</p> <p>{{license.description}}</p>
<a href="#" pfm-eat-click class="btn" ng-class="{'btn-primary': edit.license_id == license.id}"> <a href="#" pfm-eat-click class="btn" ng-class="{'btn-primary': edit.license_id == license.id, 'disabled': isSaving}">
<span ng-hide="edit.license_id == license.id">Select</span> <span ng-hide="edit.license_id == license.id">Select</span>
<span ng-show="edit.license_id == license.id">Selected</span> <span ng-show="edit.license_id == license.id">Selected</span>
</a> </a>