Merge pull request #1261 from josephnle/drag-and-drop-avatar-upload

Add drag and drop avatar uploading
This commit is contained in:
Toby Zerner 2017-11-13 01:33:29 +02:00 committed by GitHub
commit 55a09a2f57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 22 deletions

View File

@ -23,6 +23,13 @@ export default class AvatarEditor extends Component {
* @type {Boolean} * @type {Boolean}
*/ */
this.loading = false; this.loading = false;
/**
* Whether or not an image has been dragged over the dropzone.
*
* @type {Boolean}
*/
this.isDraggedOver = false;
} }
static initProps(props) { static initProps(props) {
@ -35,12 +42,17 @@ export default class AvatarEditor extends Component {
const user = this.props.user; const user = this.props.user;
return ( return (
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '')}> <div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
{avatar(user)} {avatar(user)}
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" } <a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')} title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
data-toggle="dropdown" data-toggle="dropdown"
onclick={this.quickUpload.bind(this)}> onclick={this.quickUpload.bind(this)}
ondragover={this.enableDragover.bind(this)}
ondragenter={this.enableDragover.bind(this)}
ondragleave={this.disableDragover.bind(this)}
ondragend={this.disableDragover.bind(this)}
ondrop={this.dropUpload.bind(this)}>
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))} {this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
</a> </a>
<ul className="Dropdown-menu Menu"> <ul className="Dropdown-menu Menu">
@ -62,7 +74,7 @@ export default class AvatarEditor extends Component {
Button.component({ Button.component({
icon: 'upload', icon: 'upload',
children: app.translator.trans('core.forum.user.avatar_upload_button'), children: app.translator.trans('core.forum.user.avatar_upload_button'),
onclick: this.upload.bind(this) onclick: this.openPicker.bind(this)
}) })
); );
@ -77,6 +89,40 @@ export default class AvatarEditor extends Component {
return items; return items;
} }
/**
* Enable dragover style
*
* @param {Event} e
*/
enableDragover(e) {
e.preventDefault();
e.stopPropagation();
this.isDraggedOver = true;
}
/**
* Disable dragover style
*
* @param {Event} e
*/
disableDragover(e) {
e.preventDefault();
e.stopPropagation();
this.isDraggedOver = false;
}
/**
* Upload avatar when file is dropped into dropzone.
*
* @param {Event} e
*/
dropUpload(e) {
e.preventDefault();
e.stopPropagation();
this.isDraggedOver = false;
this.upload(e.dataTransfer.files[0]);
}
/** /**
* If the user doesn't have an avatar, there's no point in showing the * If the user doesn't have an avatar, there's no point in showing the
* controls dropdown, because only one option would be viable: uploading. * controls dropdown, because only one option would be viable: uploading.
@ -89,14 +135,14 @@ export default class AvatarEditor extends Component {
if (!this.props.user.avatarUrl()) { if (!this.props.user.avatarUrl()) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.upload(); this.openPicker();
} }
} }
/** /**
* Prompt the user to upload a new avatar. * Upload avatar using file picker
*/ */
upload() { openPicker() {
if (this.loading) return; if (this.loading) return;
// Create a hidden HTML input element and click on it so the user can select // Create a hidden HTML input element and click on it so the user can select
@ -105,24 +151,36 @@ export default class AvatarEditor extends Component {
const $input = $('<input type="file">'); const $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('change', e => { $input.appendTo('body').hide().click().on('change', e => {
const data = new FormData(); this.upload($(e.target)[0].files[0]);
data.append('avatar', $(e.target)[0].files[0]);
this.loading = true;
m.redraw();
app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
}); });
} }
/**
* Upload avatar
*
* @param {File} file
*/
upload(file) {
if (this.loading) return;
const user = this.props.user;
const data = new FormData();
data.append('avatar', file);
this.loading = true;
m.redraw();
app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
}
/** /**
* Remove the user's avatar. * Remove the user's avatar.
*/ */

View File

@ -17,7 +17,10 @@
.AvatarEditor--noAvatar { .AvatarEditor--noAvatar {
opacity: 0.7; opacity: 0.7;
} }
&:hover .Dropdown-toggle, &.open .Dropdown-toggle, &.loading .Dropdown-toggle { &:hover .Dropdown-toggle,
&.open .Dropdown-toggle,
&.loading .Dropdown-toggle,
&.dragover .Dropdown-toggle {
opacity: 1; opacity: 1;
} }
.LoadingIndicator { .LoadingIndicator {