Avatar upload
This commit is contained in:
143
app/scenes/Settings/components/ImageUpload.js
Normal file
143
app/scenes/Settings/components/ImageUpload.js
Normal file
@@ -0,0 +1,143 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
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 = {
|
||||
children?: React$Element<any>,
|
||||
onSuccess: string => void,
|
||||
onError: string => void,
|
||||
};
|
||||
|
||||
@observer
|
||||
class DropToImport extends Component {
|
||||
@observable isUploading: boolean = false;
|
||||
@observable isCropping: boolean = false;
|
||||
@observable zoom: number = 1;
|
||||
props: Props;
|
||||
file: File;
|
||||
avatarEditorRef: HTMLCanvasElement;
|
||||
|
||||
onDropAccepted = async (files: File[]) => {
|
||||
this.isCropping = true;
|
||||
this.file = files[0];
|
||||
};
|
||||
|
||||
handleCrop = async () => {
|
||||
// $FlowIssue getImage() exists
|
||||
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) {
|
||||
this.props.onError('Unable to upload image');
|
||||
} finally {
|
||||
this.isUploading = false;
|
||||
this.isCropping = false;
|
||||
}
|
||||
};
|
||||
|
||||
handleZoom = (event: SyntheticDragEvent) => {
|
||||
let target = event.target;
|
||||
if (target instanceof HTMLInputElement) {
|
||||
this.zoom = parseFloat(target.value);
|
||||
}
|
||||
};
|
||||
|
||||
renderCropping() {
|
||||
return (
|
||||
<Modal isOpen title="">
|
||||
<Flex auto column align="center" justify="center">
|
||||
<AvatarEditorContainer>
|
||||
<AvatarEditor
|
||||
ref={ref => (this.avatarEditorRef = ref)}
|
||||
image={this.file}
|
||||
width={250}
|
||||
height={250}
|
||||
border={25}
|
||||
borderRadius={150}
|
||||
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}
|
||||
/>
|
||||
{this.isUploading && <LoadingIndicator />}
|
||||
<CropButton onClick={this.handleCrop} disabled={this.isUploading}>
|
||||
Crop avatar
|
||||
</CropButton>
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.isCropping) {
|
||||
return this.renderCropping();
|
||||
} else {
|
||||
return (
|
||||
<Dropzone
|
||||
accept="image/png, image/jpeg"
|
||||
onDropAccepted={this.onDropAccepted}
|
||||
style={{}}
|
||||
disablePreview
|
||||
{...this.props}
|
||||
>
|
||||
{this.props.children}
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user