Skip to content

Commit

Permalink
added drag and drop functionality for image uploads (#182)
Browse files Browse the repository at this point in the history
* added drag and drop functionality and modified conditions to support png and jpeg file formats

* remove a extra bracket

* updated file size limit to 25MB

* Fix linting issues
  • Loading branch information
antonyr authored Jul 25, 2024
1 parent c8facfc commit 179b5e8
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 33 deletions.
165 changes: 133 additions & 32 deletions frontend/src/components/files/UploadImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import imageCompression from "browser-image-compression";
import TCButton from "components/files/TCButton";
import { api } from "hooks/api";
import { useAuthentication } from "hooks/auth";
import React, { useState } from "react";
import { Alert, Col, Form } from "react-bootstrap";
import React, { useEffect, useRef, useState } from "react";
import { Alert, Col } from "react-bootstrap";

interface ImageUploadProps {
onUploadSuccess: (url: string) => void;
Expand All @@ -16,38 +16,106 @@ const ImageUploadComponent: React.FC<ImageUploadProps> = ({
const [compressedFile, setCompressedFile] = useState<File | null>(null);
const [uploadStatus, setUploadStatus] = useState<string | null>(null);
const [fileError, setFileError] = useState<string | null>(null);
const [dragOver, setDragOver] = useState<boolean>(false);
const auth = useAuthentication();
const auth_api = new api(auth.api);
const MAX_FILE_SIZE = 2 * 1024 * 1024;
const handleFileChange = async (
const MAX_FILE_SIZE = 25 * 1024 * 1024;
const validFileTypes = ["image/png", "image/jpeg", "image/jpg"];

const fileInputRef = useRef<HTMLInputElement | null>(null);

useEffect(() => {
const handleWindowDrop = async (event: DragEvent) => {
event.preventDefault();
setDragOver(false);
const file = event.dataTransfer?.files[0];
if (file) {
handleFileChange(file);
}
};

const handleWindowDragOver = (event: DragEvent) => {
event.preventDefault();
setDragOver(true);
};

const handleWindowDragLeave = () => {
setDragOver(false);
};

window.addEventListener("drop", handleWindowDrop);
window.addEventListener("dragover", handleWindowDragOver);
window.addEventListener("dragleave", handleWindowDragLeave);

return () => {
window.removeEventListener("drop", handleWindowDrop);
window.removeEventListener("dragover", handleWindowDragOver);
window.removeEventListener("dragleave", handleWindowDragLeave);
};
}, []);

const handleFileChange = async (file: File) => {
setUploadStatus(null);

// Validate file type
if (!validFileTypes.includes(file.type)) {
setFileError("Only PNG, JPG, and JPEG files are allowed");
setSelectedFile(null);
return;
}

// Validate file size
if (file.size > MAX_FILE_SIZE) {
setFileError(
`File size should not exceed ${MAX_FILE_SIZE / 1024 / 1024} MB`,
);
setSelectedFile(null);
return;
}

const options = {
maxSizeMB: 0.2, // Maximum size in MB
maxWidthOrHeight: 800, // Maximum width or height in pixels
useWebWorker: true, // Use multi-threading for compression
};

try {
const thecompressedFile = await imageCompression(file, options);
setCompressedFile(thecompressedFile);
setSelectedFile(file);
setFileError(null);
} catch (error) {
console.error("Error compressing the image:", error);
setFileError("Error compressing the image");
setSelectedFile(null);
}
};

const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setDragOver(true);
};

const handleDragLeave = () => {
setDragOver(false);
};

const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setDragOver(false);
const file = event.dataTransfer.files[0];
if (file) {
handleFileChange(file);
}
};

const handleFileInputChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
if (event.target.files) {
const file = event.target.files[0];
if (file) {
setUploadStatus(null);
if (file.size > MAX_FILE_SIZE) {
setFileError(
`File size should not exceed ${MAX_FILE_SIZE / 1024 / 1024} MB`,
);
} else {
const options = {
maxSizeMB: 0.2, // Maximum size in MB
maxWidthOrHeight: 800, // Maximum width or height in pixels
useWebWorker: true, // Use multi-threading for compression
};
try {
const thecompressedFile = await imageCompression(file, options);
setCompressedFile(thecompressedFile);
} catch (error) {
console.error("Error compressing the image:", error);
setFileError("Error compressing the image");
}
setSelectedFile(file);
setFileError(null);
}
} else {
setFileError("No file selected");
handleFileChange(file);
}
}
};
Expand Down Expand Up @@ -75,13 +143,46 @@ const ImageUploadComponent: React.FC<ImageUploadProps> = ({
}
};

const triggerFileInput = () => {
fileInputRef.current?.click();
};

return (
<Col md="6">
<div>
<Form.Group controlId="formFile" className="mb-3">
<Form.Label>Select Image</Form.Label>
<Form.Control type="file" onChange={handleFileChange} accept=".png" />
</Form.Group>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
style={{
border: dragOver ? "2px dashed #000" : "2px dashed transparent",
padding: "10px",
borderRadius: "5px",
textAlign: "center",
marginBottom: "20px",
}}
>
<div
onClick={triggerFileInput}
style={{
cursor: "pointer",
background: "black",
border: "1px solid #ccc",
borderRadius: "5px",
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "40px",
}}
>
{selectedFile ? selectedFile.name : "No file chosen"}
</div>
<input
type="file"
accept=".png,.jpg,.jpeg"
onChange={handleFileInputChange}
ref={fileInputRef}
style={{ display: "none" }}
/>
{fileError && <Alert variant="danger">{fileError}</Alert>}
<TCButton onClick={handleUpload} disabled={!selectedFile}>
Upload
Expand Down
2 changes: 1 addition & 1 deletion store/app/routers/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UserInfoResponse(BaseModel):
@image_router.post("/upload/")
async def upload_image(crud: Annotated[Crud, Depends(Crud.get)], file: UploadFile) -> UserInfoResponse:
try:
if file.content_type != "image/png":
if file.content_type in ["image/png", "image/jpeg", "image/jpg"]:
raise HTTPException(status_code=400, detail="Only PNG images are supported")
if len(await file.read()) > 1024 * 1024 * 2:
raise HTTPException(status_code=400, detail="Image is too large")
Expand Down

0 comments on commit 179b5e8

Please sign in to comment.