aboutsummaryrefslogtreecommitdiff
path: root/src/containers
diff options
context:
space:
mode:
Diffstat (limited to 'src/containers')
-rw-r--r--src/containers/AvatarCropModal/AvatarCropModal.tsx57
-rw-r--r--src/containers/AvatarCropModal/canvasUtils.js54
-rw-r--r--src/containers/Profile/ProfileInfo.tsx15
3 files changed, 117 insertions, 9 deletions
diff --git a/src/containers/AvatarCropModal/AvatarCropModal.tsx b/src/containers/AvatarCropModal/AvatarCropModal.tsx
new file mode 100644
index 0000000..a24d2b4
--- /dev/null
+++ b/src/containers/AvatarCropModal/AvatarCropModal.tsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import SendIcon from "@material-ui/icons/Send";
+import { getCroppedImg } from './canvasUtils'
+
+import ImageCropAreaSelect from '../../components/ImageCropAreaSelect/ImageCropAreaSelect';
+import ModalScreen from '../../components/ModalScreen/ModalScreen';
+
+interface Area {
+ x: number;
+ y: number;
+ width?: number;
+ height?: number;
+}
+
+interface PropTypes {
+ avatar: string;
+ callback: (file: File) => void;
+}
+
+const useStyles = makeStyles(theme => ({
+ cropContainer: {
+ position: 'relative',
+ width: '100%',
+ height: '100vh',
+ background: '#333',
+ [theme.breakpoints.up('sm')]: {
+ height: 400,
+ },
+ }
+}));
+
+const AvatarCropModal: React.FC<PropTypes> = ({ avatar, callback }) => {
+ const classes = useStyles();
+ const [area, setArea] = useState<Area>({ x: 0, y: 0 });
+
+ const handleAction = async () => getCroppedImg(avatar, area)
+ .then(croppedImage => callback(croppedImage));
+
+ return (
+ <ModalScreen
+ title="Choose area"
+ actionIcon={<SendIcon />}
+ handleAction={handleAction}
+ isActionDisabled={false}
+ >
+ <div className={classes.cropContainer}>
+ <ImageCropAreaSelect
+ image={avatar}
+ setArea={setArea}
+ />
+ </div>
+ </ModalScreen>
+ )
+};
+
+export default AvatarCropModal;
diff --git a/src/containers/AvatarCropModal/canvasUtils.js b/src/containers/AvatarCropModal/canvasUtils.js
new file mode 100644
index 0000000..b01aadc
--- /dev/null
+++ b/src/containers/AvatarCropModal/canvasUtils.js
@@ -0,0 +1,54 @@
+const createImage = url =>
+ new Promise((resolve, reject) => {
+ const image = new Image();
+ image.addEventListener('load', () => resolve(image));
+ image.addEventListener('error', error => reject(error));
+ image.setAttribute('crossOrigin', 'anonymous') ;// needed to avoid cross-origin issues on CodeSandbox
+ image.src = url
+ });
+
+export async function getCroppedImg(imageSrc, pixelCrop) {
+ const image = await createImage(imageSrc);
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ const maxSize = Math.max(image.width, image.height);
+ const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
+
+ // set each dimensions to double largest dimension to allow for a safe area for the
+ // image to rotate in without being clipped by canvas context
+ canvas.width = safeArea;
+ canvas.height = safeArea;
+
+ // translate canvas context to a central location on image to allow rotating around the center.
+ ctx.translate(safeArea / 2, safeArea / 2);
+ ctx.translate(-safeArea / 2, -safeArea / 2);
+
+ // draw rotated image and store data.
+ ctx.drawImage(
+ image,
+ safeArea / 2 - image.width * 0.5,
+ safeArea / 2 - image.height * 0.5
+ );
+ const data = ctx.getImageData(0, 0, safeArea, safeArea);
+
+ // set canvas width to final desired crop size - this will clear existing context
+ canvas.width = pixelCrop.width;
+ canvas.height = pixelCrop.height;
+
+ // paste generated rotate image with correct offsets for x,y crop values.
+ ctx.putImageData(
+ data,
+ Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
+ Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
+ );
+
+ // As Base64 string
+ // return canvas.toDataURL('image/jpeg');
+
+ // As a blob
+
+ return new Promise(resolve => {
+ canvas.toBlob(file => resolve(file))
+ })
+}
diff --git a/src/containers/Profile/ProfileInfo.tsx b/src/containers/Profile/ProfileInfo.tsx
index b71e6db..4c9f17e 100644
--- a/src/containers/Profile/ProfileInfo.tsx
+++ b/src/containers/Profile/ProfileInfo.tsx
@@ -11,7 +11,7 @@ import Avatar from '../../components/Avatar/Avatar';
import { patch } from '../../requests';
import { useAuth } from '../../hooks/useAuth';
import uploadFileToS3 from '../../utils/uploadFileToS3';
-import AvatarCrop from "../../components/AvatarCrop/AvatarCrop";
+import AvatarCropModal from '../AvatarCropModal/AvatarCropModal';
interface PropTypes {
savedPolls: number;
@@ -112,13 +112,10 @@ const ProfileInfo: React.FC<PropTypes> = ({
const dateSince = useMemo(() => formatDate(userInfo?.createdAt), [userInfo]);
const handleUpdateAvatar = useCallback(async (file: File) => {
- if (user) {
- uploadFileToS3(file, 0.8, setProgress)
- .then(avatarUrl => patch(`/users/${user._id}`, { avatarUrl }))
- .then(response => setUserInfo(response.data))
- .then(() => setProgress(0));
- setAvatarToCrop('');
- }
+ if (user) uploadFileToS3(file, 0.8, setProgress)
+ .then(avatarUrl => patch(`/users/${user._id}`, { avatarUrl }))
+ .then(response => setUserInfo(response.data))
+ .then(() => setProgress(0));
}, [user, setUserInfo]);
const handleCropAvatar = useCallback( async(file: File) => {
@@ -129,7 +126,7 @@ const ProfileInfo: React.FC<PropTypes> = ({
return (
<div className={classes.root}>
{
- avatarToCrop && <AvatarCrop avatarToCrop={avatarToCrop} setAvatarToCrop={setAvatarToCrop} callback={handleUpdateAvatar}/>
+ avatarToCrop && <AvatarCropModal avatar={avatarToCrop} callback={handleUpdateAvatar}/>
}
{
!userInfo