From ae096d4b7e991b2169f75dd1844b4d36254a8183 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 13 Oct 2023 21:33:47 +0200 Subject: [PATCH] basic avatar upload --- src/Components/Profile/Settings.tsx | 194 +++++++++++++++++++---- src/Components/Templates/DialogModal.tsx | 2 +- src/index.css | 8 + 3 files changed, 168 insertions(+), 36 deletions(-) diff --git a/src/Components/Profile/Settings.tsx b/src/Components/Profile/Settings.tsx index 7456ecca..0fd66e7a 100644 --- a/src/Components/Profile/Settings.tsx +++ b/src/Components/Profile/Settings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { TitleCard } from '../Templates/TitleCard' import { TextInput } from '../Input/TextInput' import { TextAreaInput } from '../Input/TextAreaInput' @@ -6,12 +6,14 @@ import { toast } from 'react-toastify'; import { useNavigate } from 'react-router-dom' import { useAuth } from '../Auth'; import * as React from 'react' - +import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop' +import 'react-image-crop/dist/ReactCrop.css' import 'react-toastify/dist/ReactToastify.css'; import { UserItem } from '../../types'; +import DialogModal from '../Templates/DialogModal'; +import { useAssetApi } from '../AppShell/hooks/useAssets'; export function Settings() { - const { user, updateUser, loading, token } = useAuth(); const [id, setId] = useState(""); @@ -19,11 +21,17 @@ export function Settings() { const [text, setText] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [avatar, setAvatar] = useState(""); const [passwordChanged, setPasswordChanged] = useState(false); + const [crop, setCrop] = useState(); + const [image, setImage] = useState(""); + const [cropModalOpen, setCropModalOpen] = useState(false); + const [cropping, setCropping] = useState(false); - + const assetsApi = useAssetApi(); + const navigate = useNavigate(); useEffect(() => { setId(user?.id ? user.id : ""); @@ -31,19 +39,112 @@ export function Settings() { setText(user?.description ? user.description : ""); setEmail(user?.email ? user.email : ""); setPassword(user?.password ? user.password : ""); + setAvatar(user?.avatar ? user?.avatar : "") }, [user]) - const navigate = useNavigate(); + const imgRef = useRef(null) + + const onImageChange = (event) => { + if (event.target.files && event.target.files[0]) { + setImage(URL.createObjectURL(event.target.files[0])); + } + setCropModalOpen(true); + } + + function onImageLoad(e: React.SyntheticEvent) { + const { width, height } = e.currentTarget + console.log(width); + + setCrop(centerAspectCrop(width, height, 1)) + } + + + // This is to demonstate how to make and center a % aspect crop + // which is a bit trickier so we use some helper functions. + function centerAspectCrop( + mediaWidth: number, + mediaHeight: number, + aspect: number, + ) { + return centerCrop( + makeAspectCrop( + { + unit: 'px', + width: mediaWidth / 2, + }, + aspect, + mediaWidth, + mediaHeight, + ), + mediaWidth, + mediaHeight, + ) + } + + async function renderCrop() { + // get the image element + const image = imgRef.current; + if (crop && image) { + + const scaleX = image.naturalWidth / image.width + const scaleY = image.naturalHeight / image.height + + // create a canvas element to draw the cropped image + const canvas = new OffscreenCanvas( + crop.width * scaleX, + crop.height * scaleY, + ) + const ctx = canvas.getContext("2d"); + const pixelRatio = window.devicePixelRatio; + canvas.width = crop.width * pixelRatio * scaleX; + canvas.height = crop.height * pixelRatio * scaleY; + + if (ctx) { + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + + ctx.drawImage( + image, + crop.x * scaleX, + crop.y * scaleY, + crop.width * scaleX, + crop.height * scaleY, + 0, + 0, + crop.width * scaleX, + crop.height * scaleY + ); + } + const blob = await canvas.convertToBlob(); + await resizeBlob(blob); + setCropping(false); + setImage(""); + } + } + + async function resizeBlob(blob) { + var img = new Image(); + img.src = URL.createObjectURL(blob); + await img.decode(); + const canvas = new OffscreenCanvas( + 400, + 400 + ) + var ctx = canvas.getContext("2d"); + ctx?.drawImage(img, 0, 0, 400, 400); + const resizedBlob = await canvas.convertToBlob() + const asset = await assetsApi.upload(resizedBlob, "test"); + setAvatar(asset.id) + } const onUpdateUser = () => { let changedUser = {} as UserItem; if (passwordChanged) { - changedUser = { id: id, first_name: name, description: text, email: email, password: password }; + changedUser = { id: id, first_name: name, description: text, email: email, avatar: avatar, password: password }; } else { - changedUser = { id: id, first_name: name, description: text, email: email }; + changedUser = { id: id, first_name: name, description: text, email: email, avatar: avatar }; } toast.promise( @@ -56,39 +157,62 @@ export function Settings() { .then(() => navigate("/")); } - return ( -
-
- -
- setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto '/> -
-
- - -
-
- setText(v)} inputStyle='tw-h-64' /> -
-
+ <> +
+
+ +
+ {!cropping && avatar ? +
+ +
+ setText(v)} inputStyle='tw-h-64' /> +
+
-
- setEmail(v)} /> - { - setPassword(v); - setPasswordChanged(true); - }} /> - {/* */} -
+
+ setEmail(v)} /> + { + setPassword(v); + setPasswordChanged(true); + }} /> + {/* */} +
-
+
-
-
-
+
+
+
+ { + setCropModalOpen(false); + setImage(""); + }}> + setCrop(c)} aspect={1} > + + + + + ) } diff --git a/src/Components/Templates/DialogModal.tsx b/src/Components/Templates/DialogModal.tsx index 74e13f5b..12311e81 100644 --- a/src/Components/Templates/DialogModal.tsx +++ b/src/Components/Templates/DialogModal.tsx @@ -43,7 +43,7 @@ const DialogModal = ({ return ( -