mirror of
https://github.com/flarum/framework.git
synced 2025-04-28 15:44:03 +08:00
228 lines
5.3 KiB
JavaScript
228 lines
5.3 KiB
JavaScript
import Component from 'flarum/Component';
|
|
import avatar from 'flarum/helpers/avatar';
|
|
import icon from 'flarum/helpers/icon';
|
|
import listItems from 'flarum/helpers/listItems';
|
|
import ItemList from 'flarum/utils/ItemList';
|
|
import Button from 'flarum/components/Button';
|
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
|
|
|
/**
|
|
* The `AvatarEditor` component displays a user's avatar along with a dropdown
|
|
* menu which allows the user to upload/remove the avatar.
|
|
*
|
|
* ### Props
|
|
*
|
|
* - `className`
|
|
* - `user`
|
|
*/
|
|
export default class AvatarEditor extends Component {
|
|
init() {
|
|
/**
|
|
* Whether or not an avatar upload is in progress.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
this.loading = false;
|
|
|
|
/**
|
|
* Whether or not an image has been dragged over the dropzone.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
this.isDraggedOver = false;
|
|
}
|
|
|
|
static initProps(props) {
|
|
super.initProps(props);
|
|
|
|
props.className = props.className || '';
|
|
}
|
|
|
|
view() {
|
|
const user = this.props.user;
|
|
|
|
return (
|
|
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
|
|
{avatar(user)}
|
|
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
|
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
|
data-toggle="dropdown"
|
|
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'))}
|
|
</a>
|
|
<ul className="Dropdown-menu Menu">
|
|
{listItems(this.controlItems().toArray())}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the items in the edit avatar dropdown menu.
|
|
*
|
|
* @return {ItemList}
|
|
*/
|
|
controlItems() {
|
|
const items = new ItemList();
|
|
|
|
items.add('upload',
|
|
Button.component({
|
|
icon: 'upload',
|
|
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
|
onclick: this.openPicker.bind(this)
|
|
})
|
|
);
|
|
|
|
items.add('remove',
|
|
Button.component({
|
|
icon: 'times',
|
|
children: app.translator.trans('core.forum.user.avatar_remove_button'),
|
|
onclick: this.remove.bind(this)
|
|
})
|
|
);
|
|
|
|
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
|
|
* controls dropdown, because only one option would be viable: uploading.
|
|
* Thus, when the avatar editor's dropdown toggle button is clicked, we prompt
|
|
* the user to upload an avatar immediately.
|
|
*
|
|
* @param {Event} e
|
|
*/
|
|
quickUpload(e) {
|
|
if (!this.props.user.avatarUrl()) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.openPicker();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload avatar using file picker
|
|
*/
|
|
openPicker() {
|
|
if (this.loading) return;
|
|
|
|
// Create a hidden HTML input element and click on it so the user can select
|
|
// an avatar file. Once they have, we will upload it via the API.
|
|
const user = this.props.user;
|
|
const $input = $('<input type="file">');
|
|
|
|
$input.appendTo('body').hide().click().on('change', e => {
|
|
this.upload($(e.target)[0].files[0]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
const user = this.props.user;
|
|
|
|
this.loading = true;
|
|
m.redraw();
|
|
|
|
app.request({
|
|
method: 'DELETE',
|
|
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar'
|
|
}).then(
|
|
this.success.bind(this),
|
|
this.failure.bind(this)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* After a successful upload/removal, push the updated user data into the
|
|
* store, and force a recomputation of the user's avatar color.
|
|
*
|
|
* @param {Object} response
|
|
* @protected
|
|
*/
|
|
success(response) {
|
|
app.store.pushPayload(response);
|
|
delete this.props.user.avatarColor;
|
|
|
|
this.loading = false;
|
|
m.redraw();
|
|
}
|
|
|
|
/**
|
|
* If avatar upload/removal fails, stop loading.
|
|
*
|
|
* @param {Object} response
|
|
* @protected
|
|
*/
|
|
failure(response) {
|
|
this.loading = false;
|
|
m.redraw();
|
|
}
|
|
}
|