Update display of avatar upload menu (#50)

* Update display of avatar upload menu

* Add i18n

* Incorporate feedback

* Add mobile style fix

* Fix loading style

* Use ===
This commit is contained in:
Adam Butterworth
2019-03-01 17:14:32 -05:00
committed by GitHub
parent a1706dcf7f
commit af8fb0e859
3 changed files with 129 additions and 51 deletions

View File

@@ -101,6 +101,7 @@ export class ProfilePage extends React.Component {
onSave={this.handleSaveProfilePhoto}
onDelete={this.handleDeleteProfilePhoto}
savePhotoState={this.props.savePhotoState}
isEditable={this.props.isCurrentUserProfile}
/>
<div>
<h2 className="mb-0">{username}</h2>
@@ -201,7 +202,8 @@ ProfilePage.defaultProps = {
const mapStateToProps = (state) => {
const profileImage =
state.profilePage.account.profileImage != null
? state.profilePage.account.profileImage.imageUrlLarge
// TODO: This will change back to camelcase in the future
? state.profilePage.account.profileImage.image_url_large
: null;
return {
isCurrentUserProfile: state.userAccount.username === state.profilePage.account.username,

View File

@@ -1,21 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input, Spinner } from 'reactstrap';
import { Input, Spinner, Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Button } from 'reactstrap';
import { FormattedMessage } from 'react-intl';
class ProfileAvatar extends React.Component {
constructor(props) {
super(props);
this.state = {
dropdownOpen: false,
};
this.fileInput = React.createRef();
this.form = React.createRef();
this.onClick = this.onClick.bind(this);
this.onClickUpload = this.onClickUpload.bind(this);
this.onClickDelete = this.onClickDelete.bind(this);
this.onInput = this.onInput.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.toggleDropdown = this.toggleDropdown.bind(this);
}
onClick() {
onClickUpload() {
this.fileInput.current.click();
}
@@ -32,58 +39,104 @@ class ProfileAvatar extends React.Component {
this.props.onSave(new FormData(this.form.current));
}
render() {
const {
src,
} = this.props;
toggleDropdown() {
this.setState({
dropdownOpen: !this.state.dropdownOpen,
});
}
renderPending() {
return (
<div
className="position-absolute w-100 h-100 d-flex justify-content-center align-items-center rounded-circle"
style={{ backgroundColor: 'rgba(0,0,0,.65)' }}
>
<Spinner color="primary" />
</div>
);
}
renderMenu() {
if (!this.props.isEditable) return null;
// TODO: only checking for null now. We need a way to
// check if this src is the default image
const hasImage = this.props.src === null;
if (hasImage) {
return (
<Button className="text-white btn-block" color="link" size="sm">
<FormattedMessage
id="profile.profileavatar.upload-button"
defaultMessage="Upload Photo"
description="Upload photo button"
/>
</Button>
);
}
return (
<div className="profile-avatar-wrap position-relative">
<div className="profile-avatar rounded-circle overflow-hidden bg-dark">
{this.props.savePhotoState === 'pending' ? (
<div
className="p-absolute w-100 h-100 d-flex justify-content-center align-items-center"
style={{ backgroundColor: 'rgba(255,255,255,.5)' }}
>
<Spinner color="primary" />
</div>
) : null}
<button
className="text-white profile-avatar-edit-button"
onClick={this.onClick}
>
Change
</button>
<form
ref={this.form}
onSubmit={this.onSubmit}
encType="multipart/form-data"
>
<img className="w-100" src={src} alt="profile avatar" />
{/* The name of this input must be 'file' */}
<Input
className="d-none"
innerRef={this.fileInput}
type="file"
name="file"
id="exampleFile"
onInput={this.onInput}
accept=".jpg, .jpeg, .png"
<Dropdown
isOpen={this.state.dropdownOpen}
toggle={this.toggleDropdown}
>
<DropdownToggle className="text-white btn-block" color="link" size="sm">
<FormattedMessage
id="profile.profileavatar.change-button"
defaultMessage="Change"
description="Change photo button"
/>
</DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={this.onClickUpload}>
<FormattedMessage
id="profile.profileavatar.upload-button"
defaultMessage="Upload Photo"
description="Upload photo button"
/>
</form>
</DropdownItem>
<DropdownItem onClick={this.onClickDelete}>
<FormattedMessage
id="profile.profileavatar.remove.button"
defaultMessage="Remove"
description="Remove photo button"
/>
</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}
render() {
return (
<div className="profile-avatar-wrap position-relative">
<div className="profile-avatar rounded-circle bg-dark">
<div className="profile-avatar-menu-container">
{this.props.savePhotoState === 'pending' ? this.renderPending() : this.renderMenu() }
</div>
<img
className="w-100 h-100 d-block rounded-circle overflow-hidden"
style={{ objectFit: 'cover' }}
alt="profile avatar"
src={this.props.src}
/>
</div>
{src ? (
<button
className="position-absolute btn btn-link w-100 btn-sm"
onClick={this.onClickDelete}
>
Remove
</button>
) : null}
<form
ref={this.form}
onSubmit={this.onSubmit}
encType="multipart/form-data"
>
{/* The name of this input must be 'file' */}
<Input
className="d-none"
innerRef={this.fileInput}
type="file"
name="file"
id="photo-file"
onInput={this.onInput}
accept=".jpg, .jpeg, .png"
/>
</form>
</div>
);
}
@@ -97,9 +150,11 @@ ProfileAvatar.propTypes = {
onSave: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
savePhotoState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
isEditable: PropTypes.bool,
};
ProfileAvatar.defaultProps = {
src: null,
savePhotoState: null,
isEditable: false,
};

View File

@@ -57,6 +57,27 @@ $fa-font-path: "~font-awesome/fonts";
}
}
.profile-avatar-menu-container {
background: rgba(0,0,0,.65);
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
@include media-breakpoint-up(md) {
background: linear-gradient(to top, rgba(0,0,0,.65) 4rem, rgba(0,0,0,0) 4rem);
align-items: flex-end;
}
.btn {
text-decoration: none;
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
}
}
.profile-avatar {
width: 5rem;
height: 5rem;