2017-12-14 07:17:08 +00:00
|
|
|
// @flow
|
2018-05-05 23:16:08 +00:00
|
|
|
import * as React from 'react';
|
2017-12-14 07:17:08 +00:00
|
|
|
import { observable } from 'mobx';
|
|
|
|
import { observer } from 'mobx-react';
|
|
|
|
import styled from 'styled-components';
|
|
|
|
import Dropzone from 'react-dropzone';
|
|
|
|
import LoadingIndicator from 'components/LoadingIndicator';
|
|
|
|
import Flex from 'shared/components/Flex';
|
|
|
|
import Modal from 'components/Modal';
|
|
|
|
import Button from 'components/Button';
|
|
|
|
import AvatarEditor from 'react-avatar-editor';
|
|
|
|
import { uploadFile, dataUrlToBlob } from 'utils/uploadFile';
|
|
|
|
|
|
|
|
type Props = {
|
2018-05-05 23:16:08 +00:00
|
|
|
children?: React.Node,
|
2019-08-09 06:09:09 +00:00
|
|
|
onSuccess: string => void | Promise<void>,
|
|
|
|
onError: string => void,
|
2018-05-31 19:52:03 +00:00
|
|
|
submitText: string,
|
|
|
|
borderRadius: number,
|
2017-12-14 07:17:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
@observer
|
2018-05-05 23:16:08 +00:00
|
|
|
class DropToImport extends React.Component<Props> {
|
2017-12-14 07:17:08 +00:00
|
|
|
@observable isUploading: boolean = false;
|
|
|
|
@observable isCropping: boolean = false;
|
|
|
|
@observable zoom: number = 1;
|
|
|
|
file: File;
|
2017-12-17 22:14:49 +00:00
|
|
|
avatarEditorRef: AvatarEditor;
|
2017-12-14 07:17:08 +00:00
|
|
|
|
2018-05-31 19:52:03 +00:00
|
|
|
static defaultProps = {
|
|
|
|
submitText: 'Crop Picture',
|
|
|
|
borderRadius: 150,
|
|
|
|
};
|
|
|
|
|
2017-12-14 07:17:08 +00:00
|
|
|
onDropAccepted = async (files: File[]) => {
|
|
|
|
this.isCropping = true;
|
|
|
|
this.file = files[0];
|
|
|
|
};
|
|
|
|
|
2018-11-18 02:25:10 +00:00
|
|
|
handleCrop = () => {
|
2018-10-01 01:09:35 +00:00
|
|
|
this.isUploading = true;
|
2018-11-18 02:25:10 +00:00
|
|
|
|
|
|
|
// allow the UI to update before converting the canvas to a Blob
|
|
|
|
// for large images this can cause the page rendering to hang.
|
|
|
|
setImmediate(this.uploadImage);
|
|
|
|
};
|
|
|
|
|
|
|
|
uploadImage = async () => {
|
2017-12-14 07:17:08 +00:00
|
|
|
const canvas = this.avatarEditorRef.getImage();
|
|
|
|
const imageBlob = dataUrlToBlob(canvas.toDataURL());
|
|
|
|
try {
|
|
|
|
const asset = await uploadFile(imageBlob, { name: this.file.name });
|
|
|
|
this.props.onSuccess(asset.url);
|
|
|
|
} catch (err) {
|
2018-06-01 22:00:48 +00:00
|
|
|
this.props.onError(err.message);
|
2017-12-14 07:17:08 +00:00
|
|
|
} finally {
|
|
|
|
this.isUploading = false;
|
|
|
|
this.isCropping = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-05-31 19:52:03 +00:00
|
|
|
handleClose = () => {
|
|
|
|
this.isUploading = false;
|
|
|
|
this.isCropping = false;
|
|
|
|
};
|
|
|
|
|
2018-05-05 23:16:08 +00:00
|
|
|
handleZoom = (event: SyntheticDragEvent<*>) => {
|
2017-12-14 07:17:08 +00:00
|
|
|
let target = event.target;
|
|
|
|
if (target instanceof HTMLInputElement) {
|
|
|
|
this.zoom = parseFloat(target.value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
renderCropping() {
|
2018-05-31 19:52:03 +00:00
|
|
|
const { submitText } = this.props;
|
|
|
|
|
2017-12-14 07:17:08 +00:00
|
|
|
return (
|
2018-05-31 19:52:03 +00:00
|
|
|
<Modal isOpen onRequestClose={this.handleClose} title="">
|
2017-12-14 07:17:08 +00:00
|
|
|
<Flex auto column align="center" justify="center">
|
2018-10-01 01:09:35 +00:00
|
|
|
{this.isUploading && <LoadingIndicator />}
|
2017-12-14 07:17:08 +00:00
|
|
|
<AvatarEditorContainer>
|
|
|
|
<AvatarEditor
|
|
|
|
ref={ref => (this.avatarEditorRef = ref)}
|
|
|
|
image={this.file}
|
|
|
|
width={250}
|
|
|
|
height={250}
|
|
|
|
border={25}
|
2018-05-31 19:52:03 +00:00
|
|
|
borderRadius={this.props.borderRadius}
|
2017-12-14 07:17:08 +00:00
|
|
|
color={[255, 255, 255, 0.6]} // RGBA
|
|
|
|
scale={this.zoom}
|
|
|
|
rotate={0}
|
|
|
|
/>
|
|
|
|
</AvatarEditorContainer>
|
|
|
|
<RangeInput
|
|
|
|
type="range"
|
|
|
|
min="0.1"
|
|
|
|
max="2"
|
|
|
|
step="0.01"
|
|
|
|
defaultValue="1"
|
|
|
|
onChange={this.handleZoom}
|
|
|
|
/>
|
|
|
|
<CropButton onClick={this.handleCrop} disabled={this.isUploading}>
|
2018-10-01 01:09:35 +00:00
|
|
|
{this.isUploading ? 'Uploading…' : submitText}
|
2017-12-14 07:17:08 +00:00
|
|
|
</CropButton>
|
|
|
|
</Flex>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
if (this.isCropping) {
|
|
|
|
return this.renderCropping();
|
|
|
|
}
|
2018-05-31 19:52:03 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Dropzone
|
|
|
|
accept="image/png, image/jpeg"
|
|
|
|
onDropAccepted={this.onDropAccepted}
|
|
|
|
style={{}}
|
|
|
|
disablePreview
|
|
|
|
onSuccess={this.props.onSuccess}
|
|
|
|
onError={this.props.onError}
|
|
|
|
>
|
|
|
|
{this.props.children}
|
|
|
|
</Dropzone>
|
|
|
|
);
|
2017-12-14 07:17:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const AvatarEditorContainer = styled(Flex)`
|
|
|
|
margin-bottom: 30px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const RangeInput = styled.input`
|
|
|
|
display: block;
|
|
|
|
width: 300px;
|
|
|
|
margin-bottom: 30px;
|
|
|
|
height: 4px;
|
|
|
|
cursor: pointer;
|
|
|
|
color: inherit;
|
|
|
|
border-radius: 99999px;
|
|
|
|
background-color: #dee1e3;
|
|
|
|
appearance: none;
|
|
|
|
|
|
|
|
&::-webkit-slider-thumb {
|
|
|
|
-webkit-appearance: none;
|
|
|
|
height: 16px;
|
|
|
|
width: 16px;
|
|
|
|
border-radius: 50%;
|
|
|
|
background: black;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
outline: none;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const CropButton = styled(Button)`
|
|
|
|
width: 300px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
export default DropToImport;
|