diff --git a/ember/app/components/user/avatar-editor.js b/ember/app/components/user/avatar-editor.js new file mode 100644 index 000000000..1c742b7ff --- /dev/null +++ b/ember/app/components/user/avatar-editor.js @@ -0,0 +1,45 @@ +import Ember from 'ember'; + +import config from 'flarum/config/environment'; + +var $ = Ember.$; + +export default Ember.Component.extend({ + layoutName: 'components/user/avatar-editor', + classNames: ['avatar-editor', 'dropdown'], + classNameBindings: ['loading'], + + click: function(e) { + if (! this.get('user.avatarUrl')) { + e.preventDefault(); + e.stopPropagation(); + this.send('upload'); + } + }, + + actions: { + upload: function() { + if (this.get('loading')) { return; } + + var $input = $(''); + var userId = this.get('user.id'); + var component = this; + $input.appendTo('body').hide().click().on('change', function() { + var formData = new FormData(); + formData.append('avatar', $(this)[0].files[0]); + component.set('loading', true); + $.ajax({ + type: 'POST', + url: config.apiURL+'/users/'+userId+'/avatar', + data: formData, + cache: false, + contentType: false, + processData: false, + complete: function() { + component.set('loading', false); + } + }); + }); + } + } +}); diff --git a/ember/app/styles/flarum/user.less b/ember/app/styles/flarum/user.less index 0f13aa769..3c068c098 100644 --- a/ember/app/styles/flarum/user.less +++ b/ember/app/styles/flarum/user.less @@ -47,10 +47,17 @@ display: inline; vertical-align: middle; } - & .avatar { - .avatar-size(96px); + & .user-avatar { float: left; margin-left: -130px; + } + & .avatar-editor .dropdown-toggle { + margin: 4px; + line-height: 96px; + font-size: 26px; + } + & .avatar { + .avatar-size(96px); border: 4px solid #fff; .box-shadow(0 2px 6px @fl-shadow-color); } @@ -170,3 +177,30 @@ } } } + +.avatar-editor { + position: relative; + + & .dropdown-toggle { + opacity: 0; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + border-radius: 100%; + background: rgba(0, 0, 0, 0.4); + text-align: center; + text-decoration: none; + } + &:hover .dropdown-toggle, &.open .dropdown-toggle, &.loading .dropdown-toggle { + opacity: 1; + } + & .loading-indicator { + color: #fff; + } + & .dropdown-menu { + left: 35%; + top: 65%; + } +} diff --git a/ember/app/templates/components/user/avatar-editor.hbs b/ember/app/templates/components/user/avatar-editor.hbs new file mode 100644 index 000000000..7f204a6cd --- /dev/null +++ b/ember/app/templates/components/user/avatar-editor.hbs @@ -0,0 +1,12 @@ +{{user-avatar user}} + + {{#if loading}} + {{ui/loading-indicator}} + {{else}} + {{fa-icon "pencil"}} + {{/if}} + + diff --git a/ember/app/templates/components/user/user-card.hbs b/ember/app/templates/components/user/user-card.hbs index 17946bebb..0a8e80dcf 100644 --- a/ember/app/templates/components/user/user-card.hbs +++ b/ember/app/templates/components/user/user-card.hbs @@ -6,7 +6,11 @@

- {{#link-to "user" user}}{{user-avatar user}}{{user-name user}}{{/link-to}} + {{#if editable}} + {{user/avatar-editor user=user class="user-avatar"}}{{user-name user}} + {{else}} + {{#link-to "user" user}}{{user-avatar user class="user-avatar"}}{{user-name user}}{{/link-to}} + {{/if}}

{{ui/item-list items=user.badges class="badges user-badges"}} diff --git a/migrations/2015_02_24_000000_create_users_table.php b/migrations/2015_02_24_000000_create_users_table.php index f9ed47aaa..8bc363b52 100644 --- a/migrations/2015_02_24_000000_create_users_table.php +++ b/migrations/2015_02_24_000000_create_users_table.php @@ -23,6 +23,7 @@ class CreateUsersTable extends Migration { $table->string('password'); $table->text('bio')->nullable(); $table->text('bio_html')->nullable(); + $table->string('avatar_path')->nullable(); $table->dateTime('join_time')->nullable(); $table->dateTime('last_seen_time')->nullable(); $table->dateTime('read_time')->nullable(); diff --git a/src/Api/Actions/BaseAction.php b/src/Api/Actions/BaseAction.php index d9aa5077c..764acdf9d 100644 --- a/src/Api/Actions/BaseAction.php +++ b/src/Api/Actions/BaseAction.php @@ -13,8 +13,6 @@ use Response; abstract class BaseAction extends Action { - abstract protected function run(ApiParams $params); - public function __construct(Actor $actor, Dispatcher $bus) { $this->actor = $actor; @@ -40,6 +38,15 @@ abstract class BaseAction extends Action return $this->run($params); } + /** + * @param ApiParams $params + * @return mixed + */ + protected function run(ApiParams $params) + { + // Should be implemented by subclasses + } + public function hydrate($object, $params) { foreach ($params as $k => $v) { diff --git a/src/Api/Actions/Users/UploadAvatarAction.php b/src/Api/Actions/Users/UploadAvatarAction.php new file mode 100644 index 000000000..73541abb4 --- /dev/null +++ b/src/Api/Actions/Users/UploadAvatarAction.php @@ -0,0 +1,21 @@ +file('avatar'); + + $this->dispatch( + new UploadAvatarCommand($userId, $file, $this->actor->getUser()), + $routeParams + ); + + return $this->respondWithoutContent(201); + } +} diff --git a/src/Api/routes.php b/src/Api/routes.php index 8c413b27b..c1fb00eec 100644 --- a/src/Api/routes.php +++ b/src/Api/routes.php @@ -59,6 +59,11 @@ Route::group(['prefix' => 'api', 'middleware' => 'Flarum\Api\Middleware\LoginWit 'uses' => $action('Flarum\Api\Actions\Users\DeleteAction') ]); + Route::post('users/{id}/avatar', [ + 'as' => 'flarum.api.users.avatar.upload', + 'uses' => $action('Flarum\Api\Actions\Users\UploadAvatarAction') + ]); + /* |-------------------------------------------------------------------------- | Activity diff --git a/src/Core/Commands/UploadAvatarCommand.php b/src/Core/Commands/UploadAvatarCommand.php new file mode 100644 index 000000000..acbd8aaef --- /dev/null +++ b/src/Core/Commands/UploadAvatarCommand.php @@ -0,0 +1,30 @@ +userId = $userId; + $this->file = $file; + $this->actor = $actor; + } +} diff --git a/src/Core/CoreServiceProvider.php b/src/Core/CoreServiceProvider.php index 92279d98e..041d21e47 100644 --- a/src/Core/CoreServiceProvider.php +++ b/src/Core/CoreServiceProvider.php @@ -1,6 +1,7 @@ app->singleton('flarum.avatars.storage', function () { + return new Local(__DIR__.'/../../ember/public/avatars'); + }); + + $this->app->when('Flarum\Core\Handlers\Commands\UploadAvatarCommandHandler') + ->needs('League\Flysystem\FilesystemInterface') + ->give(function(Container $app) { + return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('avatars')->getDriver(); + }); } public function registerGambits() diff --git a/src/Core/Events/AvatarWillBeUploaded.php b/src/Core/Events/AvatarWillBeUploaded.php new file mode 100644 index 000000000..efb93c2aa --- /dev/null +++ b/src/Core/Events/AvatarWillBeUploaded.php @@ -0,0 +1,16 @@ +user = $user; + $this->command = $command; + } +} diff --git a/src/Core/Events/UserAvatarWasChanged.php b/src/Core/Events/UserAvatarWasChanged.php new file mode 100644 index 000000000..b83c9471d --- /dev/null +++ b/src/Core/Events/UserAvatarWasChanged.php @@ -0,0 +1,13 @@ +user = $user; + } +} diff --git a/src/Core/Handlers/Commands/UploadAvatarCommandHandler.php b/src/Core/Handlers/Commands/UploadAvatarCommandHandler.php new file mode 100644 index 000000000..5e36c6da5 --- /dev/null +++ b/src/Core/Handlers/Commands/UploadAvatarCommandHandler.php @@ -0,0 +1,60 @@ +users = $users; + $this->uploadDir = $uploadDir; + } + + public function handle(UploadAvatarCommand $command) + { + $user = $this->users->findOrFail($command->userId); + + // Make sure the current user is allowed to edit the user profile. + // This will let admins and the user themselves pass through, and + // throw an exception otherwise. + $user->assertCan($command->actor, 'edit'); + + $filename = $command->file->getFilename(); + $uploadName = Str::lower(Str::quickRandom()) . '.jpg'; + + $mount = new MountManager([ + 'source' => new Filesystem(new Local($command->file->getPath())), + 'target' => $this->uploadDir, + ]); + + $user->changeAvatarPath($uploadName); + + event(new AvatarWillBeUploaded($user, $command)); + + $mount->move("source://$filename", "target://$uploadName"); + $user->save(); + $this->dispatchEventsFor($user); + + return $user; + } +} diff --git a/src/Core/Models/User.php b/src/Core/Models/User.php index c5e4d91ec..578491041 100755 --- a/src/Core/Models/User.php +++ b/src/Core/Models/User.php @@ -10,6 +10,7 @@ use Flarum\Core\Events\UserWasRenamed; use Flarum\Core\Events\UserEmailWasChanged; use Flarum\Core\Events\UserPasswordWasChanged; use Flarum\Core\Events\UserBioWasChanged; +use Flarum\Core\Events\UserAvatarWasChanged; use Flarum\Core\Events\UserWasActivated; use Flarum\Core\Events\UserEmailWasConfirmed; @@ -210,6 +211,21 @@ class User extends Model return $this; } + /** + * Change the path of the user avatar. + * + * @param string $path + * @return $this + */ + public function changeAvatarPath($path) + { + $this->avatar_path = $path; + + $this->raise(new UserAvatarWasChanged($this)); + + return $this; + } + /** * Check if a given password matches the user's password. *