import app from '../../forum/app'; import Component from '../../common/Component'; import avatar from '../../common/helpers/avatar'; import icon from '../../common/helpers/icon'; import listItems from '../../common/helpers/listItems'; import ItemList from '../../common/utils/ItemList'; import classList from '../../common/utils/classList'; import Button from '../../common/components/Button'; import LoadingIndicator from '../../common/components/LoadingIndicator'; /** * The `AvatarEditor` component displays a user's avatar along with a dropdown * menu which allows the user to upload/remove the avatar. * * ### Attrs * * - `className` * - `user` */ export default class AvatarEditor extends Component { oninit(vnode) { super.oninit(vnode); /** * 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; } view() { const user = this.attrs.user; return (
{avatar(user)} {this.loading ? ( ) : user.avatarUrl() ? ( icon('fas fa-pencil-alt') ) : ( icon('fas fa-plus-circle') )}
); } /** * Get the items in the edit avatar dropdown menu. * * @return {ItemList} */ controlItems() { const items = new ItemList(); items.add( 'upload', ); items.add( 'remove', ); 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.attrs.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 $input = $(''); $input .appendTo('body') .hide() .click() .on('input', (e) => { this.upload($(e.target)[0].files[0]); }); } /** * Upload avatar * * @param {File} file */ upload(file) { if (this.loading) return; const user = this.attrs.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, body: data, }) .then(this.success.bind(this), this.failure.bind(this)); } /** * Remove the user's avatar. */ remove() { const user = this.attrs.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.attrs.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(); } }