2019-09-24 14:00:17 +00:00
|
|
|
import React from 'react'
|
|
|
|
import styled from 'styled-components'
|
|
|
|
import Dropzone from 'react-dropzone'
|
|
|
|
import ReactAvatarEditor from 'react-avatar-editor'
|
|
|
|
import PropTypes from 'prop-types'
|
|
|
|
import { default as ASCAvatar } from '../../avatar/index'
|
|
|
|
import accepts from 'attr-accept'
|
2019-12-04 09:36:13 +00:00
|
|
|
import Text from '../../text'
|
2019-09-24 14:00:17 +00:00
|
|
|
import { tablet } from '../../../utils/device';
|
|
|
|
|
2019-09-25 09:30:37 +00:00
|
|
|
const StyledErrorContainer = styled.div`
|
|
|
|
p{
|
|
|
|
text-align: center
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2019-09-24 14:00:17 +00:00
|
|
|
const CloseButton = styled.a`
|
|
|
|
cursor: pointer;
|
|
|
|
position: absolute;
|
|
|
|
right: 33px;
|
|
|
|
top: 4px;
|
|
|
|
width: 16px;
|
|
|
|
height: 16px;
|
|
|
|
|
|
|
|
&:before, &:after {
|
|
|
|
position: absolute;
|
|
|
|
left: 8px;
|
|
|
|
content: ' ';
|
|
|
|
height: 16px;
|
|
|
|
width: 1px;
|
|
|
|
background-color: #D8D8D8;
|
|
|
|
}
|
|
|
|
&:before {
|
|
|
|
transform: rotate(45deg);
|
|
|
|
}
|
|
|
|
&:after {
|
|
|
|
transform: rotate(-45deg);
|
|
|
|
}
|
|
|
|
@media ${tablet} {
|
|
|
|
right: calc(50% - 147px);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const DropZoneContainer = styled.div`
|
|
|
|
display: block;
|
|
|
|
width: 100%;
|
|
|
|
height: 300px;
|
|
|
|
border: 1px dashed #ccc;
|
|
|
|
text-align: center;
|
|
|
|
padding: 10em 0;
|
|
|
|
margin: 0 auto;
|
|
|
|
p{
|
|
|
|
margin: 0;
|
|
|
|
cursor: default
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
const StyledAvatarContainer = styled.div`
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
.custom-range{
|
2019-09-26 13:27:42 +00:00
|
|
|
width: 100%;
|
|
|
|
display: block
|
2019-09-24 14:00:17 +00:00
|
|
|
}
|
|
|
|
.avatar-container{
|
|
|
|
display: inline-block;
|
|
|
|
vertical-align: top;
|
|
|
|
}
|
|
|
|
.editor-container{
|
|
|
|
display: inline-block;
|
2019-09-26 13:27:42 +00:00
|
|
|
width: auto;
|
|
|
|
padding: 0 30px;
|
2019-09-24 14:00:17 +00:00
|
|
|
position: relative;
|
|
|
|
@media ${tablet} {
|
2019-09-26 13:27:42 +00:00
|
|
|
padding: 0;
|
2019-09-24 14:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
const StyledASCAvatar = styled(ASCAvatar)`
|
|
|
|
display: block;
|
|
|
|
@media ${tablet} {
|
|
|
|
display: none
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
class AvatarEditorBody extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
image: this.props.image ? this.props.image : "",
|
|
|
|
scale: 1,
|
2019-09-25 09:30:37 +00:00
|
|
|
croppedImage: '',
|
|
|
|
|
|
|
|
errorText: null
|
2019-09-24 14:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.setEditorRef = React.createRef();
|
|
|
|
|
|
|
|
this.handleScale = this.handleScale.bind(this);
|
|
|
|
this.onWheel = this.onWheel.bind(this);
|
|
|
|
|
|
|
|
this.onTouchStart = this.onTouchStart.bind(this);
|
|
|
|
this.onTouchMove = this.onTouchMove.bind(this);
|
|
|
|
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
|
|
|
|
|
|
this.distance = this.distance.bind(this);
|
|
|
|
|
|
|
|
this.onImageChange = this.onImageChange.bind(this);
|
|
|
|
this.onImageReady = this.onImageReady.bind(this);
|
|
|
|
this.deleteImage = this.deleteImage.bind(this);
|
|
|
|
|
|
|
|
this.onDropAccepted = this.onDropAccepted.bind(this);
|
2019-09-25 09:30:37 +00:00
|
|
|
this.onDropRejected = this.onDropRejected.bind(this);
|
2019-09-26 13:27:42 +00:00
|
|
|
|
2019-09-24 14:00:17 +00:00
|
|
|
|
|
|
|
this.onPositionChange = this.onPositionChange.bind(this);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
onPositionChange(position) {
|
|
|
|
this.props.onPositionChange({
|
|
|
|
x: position.x,
|
|
|
|
y: position.y,
|
|
|
|
width: this.setEditorRef.current.getImage().width,
|
|
|
|
height: this.setEditorRef.current.getImage().height
|
|
|
|
});
|
|
|
|
}
|
|
|
|
onDropRejected(rejectedFiles) {
|
|
|
|
if (!accepts(rejectedFiles[0], this.props.accept)) {
|
|
|
|
this.props.onLoadFileError(0);
|
2019-09-25 09:30:37 +00:00
|
|
|
this.setState({
|
|
|
|
errorText: this.props.unknownTypeError
|
|
|
|
})
|
2019-09-24 14:00:17 +00:00
|
|
|
return;
|
|
|
|
} else if (rejectedFiles[0].size > this.props.maxSize) {
|
|
|
|
this.props.onLoadFileError(1);
|
2019-09-25 09:30:37 +00:00
|
|
|
this.setState({
|
|
|
|
errorText: this.props.maxSizeFileError
|
|
|
|
})
|
2019-09-24 14:00:17 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-09-25 09:30:37 +00:00
|
|
|
this.setState({
|
|
|
|
errorText: this.props.unknownError
|
|
|
|
})
|
2019-09-24 14:00:17 +00:00
|
|
|
this.props.onLoadFileError(2);
|
|
|
|
}
|
|
|
|
onDropAccepted(acceptedFiles) {
|
|
|
|
this.setState({
|
2019-09-25 09:30:37 +00:00
|
|
|
image: acceptedFiles[0],
|
|
|
|
errorText: null
|
2019-09-24 14:00:17 +00:00
|
|
|
});
|
|
|
|
this.props.onLoadFile(acceptedFiles[0]);
|
|
|
|
}
|
|
|
|
deleteImage() {
|
|
|
|
this.setState({
|
|
|
|
image: '',
|
|
|
|
croppedImage: ''
|
|
|
|
});
|
|
|
|
this.props.deleteImage();
|
|
|
|
}
|
|
|
|
onImageChange() {
|
|
|
|
this.setState({
|
|
|
|
croppedImage: this.setEditorRef.current.getImage().toDataURL()
|
|
|
|
});
|
|
|
|
this.props.onImageChange(this.setEditorRef.current.getImage().toDataURL());
|
|
|
|
}
|
|
|
|
dist = 0
|
|
|
|
scaling = false
|
|
|
|
curr_scale = 1.0
|
|
|
|
scale_factor = 1.0
|
|
|
|
distance(p1, p2) {
|
|
|
|
return (Math.sqrt(Math.pow((p1.clientX - p2.clientX), 2) + Math.pow((p1.clientY - p2.clientY), 2)));
|
|
|
|
}
|
|
|
|
onTouchStart(evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
var tt = evt.targetTouches;
|
|
|
|
if (tt.length >= 2) {
|
|
|
|
this.dist = this.distance(tt[0], tt[1]);
|
|
|
|
this.scaling = true;
|
|
|
|
} else {
|
|
|
|
this.scaling = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onTouchMove(evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
var tt = evt.targetTouches;
|
|
|
|
if (this.scaling) {
|
|
|
|
this.curr_scale = this.distance(tt[0], tt[1]) / this.dist * this.scale_factor;
|
|
|
|
let scale = (Math.round(this.curr_scale) * 10) / 10;
|
|
|
|
this.setState({
|
|
|
|
scale: scale < 1 ? 1 : scale > 5 ? 5 : scale
|
|
|
|
});
|
2019-11-25 11:07:50 +00:00
|
|
|
this.props.onSizeChange({
|
|
|
|
width: this.setEditorRef.current.getImage().width,
|
|
|
|
height: this.setEditorRef.current.getImage().height
|
|
|
|
});
|
2019-09-24 14:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
onTouchEnd(evt) {
|
|
|
|
var tt = evt.targetTouches;
|
|
|
|
if (tt.length < 2) {
|
|
|
|
this.scaling = false;
|
|
|
|
if (this.curr_scale < 1) {
|
|
|
|
this.scale_factor = 1;
|
|
|
|
} else {
|
|
|
|
if (this.curr_scale > 5) {
|
|
|
|
this.scale_factor = 5;
|
|
|
|
} else {
|
|
|
|
this.scale_factor = this.curr_scale;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.scaling = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onWheel(e) {
|
|
|
|
e = e || window.event;
|
|
|
|
const delta = e.deltaY || e.detail || e.wheelDelta;
|
|
|
|
let scale = delta > 0 && this.state.scale === 1 ? 1 : this.state.scale - (delta / 100) * 0.1;
|
|
|
|
scale = Math.round(scale * 10) / 10;
|
|
|
|
this.setState({
|
|
|
|
scale: scale < 1 ? 1 : scale > 5 ? 5 : scale
|
|
|
|
});
|
2019-11-25 11:07:50 +00:00
|
|
|
this.props.onSizeChange({
|
|
|
|
width: this.setEditorRef.current.getImage().width,
|
|
|
|
height: this.setEditorRef.current.getImage().height
|
|
|
|
});
|
2019-09-24 14:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
handleScale = e => {
|
|
|
|
const scale = parseFloat(e.target.value);
|
2019-11-26 08:34:34 +00:00
|
|
|
this.setState({ scale });
|
|
|
|
this.props.onSizeChange({
|
|
|
|
width: this.setEditorRef.current.getImage().width,
|
|
|
|
height: this.setEditorRef.current.getImage().height
|
|
|
|
});
|
2019-09-24 14:00:17 +00:00
|
|
|
};
|
|
|
|
onImageReady() {
|
|
|
|
this.setState({
|
|
|
|
croppedImage: this.setEditorRef.current.getImage().toDataURL()
|
|
|
|
});
|
2019-09-27 11:04:09 +00:00
|
|
|
this.props.onImageChange(this.setEditorRef.current.getImage().toDataURL());
|
2019-09-24 14:00:17 +00:00
|
|
|
this.props.onPositionChange({
|
|
|
|
x: 0.5,
|
|
|
|
y: 0.5,
|
|
|
|
width: this.setEditorRef.current.getImage().width,
|
|
|
|
height: this.setEditorRef.current.getImage().height
|
|
|
|
});
|
|
|
|
}
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
onWheel={this.onWheel}
|
|
|
|
onTouchStart={this.onTouchStart}
|
|
|
|
onTouchMove={this.onTouchMove}
|
|
|
|
onTouchEnd={this.onTouchEnd}
|
|
|
|
>
|
|
|
|
{this.state.image === '' ?
|
|
|
|
<Dropzone
|
|
|
|
onDropAccepted={this.onDropAccepted}
|
|
|
|
onDropRejected={this.onDropRejected}
|
2019-09-25 09:30:37 +00:00
|
|
|
maxSize={this.props.maxSize}
|
|
|
|
accept={this.props.accept}>
|
2019-09-24 14:00:17 +00:00
|
|
|
{({ getRootProps, getInputProps }) => (
|
|
|
|
<DropZoneContainer
|
|
|
|
{...getRootProps()}
|
|
|
|
>
|
|
|
|
<input {...getInputProps()} />
|
|
|
|
<p>{this.props.chooseFileLabel}</p>
|
|
|
|
</DropZoneContainer>
|
|
|
|
)}
|
|
|
|
</Dropzone> :
|
|
|
|
<StyledAvatarContainer>
|
|
|
|
<div className='editor-container'>
|
|
|
|
<ReactAvatarEditor
|
|
|
|
ref={this.setEditorRef}
|
|
|
|
width={250}
|
|
|
|
borderRadius={200}
|
|
|
|
scale={this.state.scale}
|
|
|
|
height={250}
|
|
|
|
className="react-avatar-editor"
|
|
|
|
image={this.state.image}
|
|
|
|
color={[0, 0, 0, .5]}
|
|
|
|
onImageChange={this.onImageChange}
|
|
|
|
onPositionChange={this.onPositionChange}
|
|
|
|
onImageReady={this.onImageReady}
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
id='scale'
|
|
|
|
type='range'
|
|
|
|
className='custom-range'
|
|
|
|
onChange={this.handleScale}
|
|
|
|
min={this.state.allowZoomOut ? '0.1' : '1'}
|
|
|
|
max='5'
|
|
|
|
step='0.01'
|
|
|
|
value={this.state.scale}
|
|
|
|
/>
|
|
|
|
<CloseButton onClick={this.deleteImage}></CloseButton>
|
|
|
|
</div>
|
|
|
|
<div className='avatar-container'>
|
|
|
|
<StyledASCAvatar
|
|
|
|
size='max'
|
|
|
|
role='user'
|
|
|
|
source={this.state.croppedImage}
|
|
|
|
editing={false}
|
|
|
|
/>
|
|
|
|
<StyledASCAvatar
|
|
|
|
size='big'
|
|
|
|
role='user'
|
|
|
|
source={this.state.croppedImage}
|
|
|
|
editing={false}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</StyledAvatarContainer>
|
|
|
|
}
|
2019-09-25 09:30:37 +00:00
|
|
|
<StyledErrorContainer key="errorMsg">
|
|
|
|
{this.state.errorText !== null &&
|
2019-12-04 09:36:13 +00:00
|
|
|
<Text
|
2019-09-25 09:30:37 +00:00
|
|
|
as="p"
|
|
|
|
color="#C96C27"
|
|
|
|
isBold={true}
|
2019-12-04 09:36:13 +00:00
|
|
|
>{this.state.errorText}</Text>
|
2019-09-25 09:30:37 +00:00
|
|
|
}
|
|
|
|
</StyledErrorContainer>
|
2019-09-24 14:00:17 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AvatarEditorBody.propTypes = {
|
|
|
|
onImageChange: PropTypes.func,
|
|
|
|
onPositionChange: PropTypes.func,
|
2019-11-25 11:07:50 +00:00
|
|
|
onSizeChange: PropTypes.func,
|
2019-09-24 14:00:17 +00:00
|
|
|
onLoadFileError: PropTypes.func,
|
|
|
|
onLoadFile: PropTypes.func,
|
|
|
|
deleteImage: PropTypes.func,
|
|
|
|
maxSize: PropTypes.number,
|
|
|
|
image: PropTypes.string,
|
|
|
|
accept: PropTypes.arrayOf(PropTypes.string),
|
|
|
|
chooseFileLabel: PropTypes.string,
|
2019-09-25 09:30:37 +00:00
|
|
|
unknownTypeError: PropTypes.string,
|
|
|
|
maxSizeFileError: PropTypes.string,
|
|
|
|
unknownError: PropTypes.string
|
|
|
|
|
2019-09-24 14:00:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
AvatarEditorBody.defaultProps = {
|
|
|
|
accept: ['image/png', 'image/jpeg'],
|
|
|
|
maxSize: Number.MAX_SAFE_INTEGER,
|
2019-09-25 09:30:37 +00:00
|
|
|
chooseFileLabel: "Drop files here, or click to select files",
|
|
|
|
unknownTypeError: "Unknown image file type",
|
|
|
|
maxSizeFileError: "Maximum file size exceeded",
|
|
|
|
unknownError: "Error"
|
2019-09-24 14:00:17 +00:00
|
|
|
};
|
|
|
|
export default AvatarEditorBody;
|