canvas-backend/src/components/EachComponent/UploadImage.jsx
2025-01-05 12:20:18 +06:00

288 lines
14 KiB
JavaScript

import { useContext, useRef, useState } from 'react'
import CanvasContext from '../Context/canvasContext/CanvasContext'
import { Button } from '@/components/ui/button'
import { fabric } from 'fabric'
import { ImageIcon, Trash2 } from 'lucide-react'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import Resizer from "react-image-file-resizer"
import { Slider } from '@/components/ui/slider'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { useDropzone } from 'react-dropzone'
import ImageCustomization from './Customization/ImageCustomization'
import { Separator } from '../ui/separator'
import ActiveObjectContext from '../Context/activeObject/ObjectContext'
const UploadImage = () => {
const { canvas } = useContext(CanvasContext);
const [width, setWidth] = useState(1080);
const [height, setHeight] = useState(1080);
const [quality, setQuality] = useState(100);
const [rotation, setRotation] = useState("0");
const [format, setFormat] = useState('JPEG');
const fileInputRef = useRef(null);
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
const [file, setFile] = useState(null);
const [preview, setPreview] = useState(null);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
accept: {
'image/*': ['.jpeg', '.png', '.gif', '.jpg', '.webp', '.svg']
},
// maxSize: 5 * 1024 * 1024, // 5MB max file size
multiple: false,
onDrop: (acceptedFiles) => {
if (!acceptedFiles.length) {
console.error("No files were dropped.");
return;
}
const selectedFile = acceptedFiles[0];
// Create a preview URL
const blobUrl = URL.createObjectURL(selectedFile);
setFile(selectedFile);
setPreview(blobUrl);
if (selectedFile.type === "image/svg+xml") {
addImageToCanvas(selectedFile);
URL.revokeObjectURL(blobUrl);
} else {
const imgElement = new Image();
imgElement.src = blobUrl;
imgElement.onload = () => {
if (imgElement.width > 1080) {
handleResize(selectedFile, (compressedFile) => {
addImageToCanvas(compressedFile);
});
} else {
addImageToCanvas(selectedFile);
}
URL.revokeObjectURL(blobUrl); // Clean up
};
imgElement.onerror = () => {
console.error("Failed to load image.");
URL.revokeObjectURL(blobUrl); // Clean up
};
}
}
});
const removeFile = () => {
// Revoke the object URL to free up memory
if (preview) {
URL.revokeObjectURL(preview)
}
setFile(null)
setPreview(null)
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
if (activeObject?.type === "image") {
canvas.remove(activeObject);
setActiveObject(null);
canvas.renderAll();
}
}
const handleResize = (file, callback) => {
Resizer.imageFileResizer(
file,
width,
height,
format,
quality,
parseInt(rotation),
(resizedFile) => {
callback(resizedFile)
},
'file',
)
}
const addImageToCanvas = (file) => {
const blobUrl = URL.createObjectURL(file)
fabric.Image.fromURL(blobUrl, (img) => {
img.set({
left: canvas.width / 4,
top: canvas.height / 4,
scaleX: 0.5,
scaleY: 0.5,
})
canvas.add(img)
canvas.setActiveObject(img);
// Update the active object state
setActiveObject(img);
canvas.renderAll();
URL.revokeObjectURL(blobUrl);
})
}
return (
<Card className="w-full mx-auto">
<CardHeader className="px-4 py-3">
<CardTitle>Image Upload & Editing</CardTitle>
<CardDescription>Upload, resize, and apply effects to your images</CardDescription>
</CardHeader>
<CardContent className="p-2">
<Tabs defaultValue="upload">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="upload">Upload</TabsTrigger>
<TabsTrigger value="effects">Effects</TabsTrigger>
</TabsList>
{/* Uploads */}
<TabsContent value="upload">
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Width: {width}px</Label>
<Slider
value={[width]}
max={2000}
min={300}
step={10}
onValueChange={(value) => setWidth(value[0])}
/>
</div>
<div className="space-y-2">
<Label>Height: {height}px</Label>
<Slider
value={[height]}
max={2000}
min={300}
step={10}
onValueChange={(value) => setHeight(value[0])}
/>
</div>
</div>
{format === "JPEG" && (
<div className="space-y-2">
<Label>Quality: {quality}%</Label>
<Slider
value={[quality]}
max={100}
step={1}
onValueChange={(value) => setQuality(value[0])}
/>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Format</Label>
<Select value={format} onValueChange={setFormat}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="JPEG">JPEG</SelectItem>
<SelectItem value="PNG">PNG</SelectItem>
<SelectItem value="WEBP">WEBP</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Rotation</Label>
<Select value={rotation} onValueChange={setRotation}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">0°</SelectItem>
<SelectItem value="45">45°</SelectItem>
<SelectItem value="90">90°</SelectItem>
<SelectItem value="180">180°</SelectItem>
<SelectItem value="270">270°</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* upload image */}
{
!preview &&
<div className="max-w-md mx-auto p-4">
<Card>
<CardContent className="p-6 space-y-4">
<div
{...getRootProps()}
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors duration-300 ${isDragActive ? 'border-primary bg-primary/10' : 'border-gray-300 hover:border-primary'} ${preview ? 'hidden' : ''}`}
ref={fileInputRef}
>
<input {...getInputProps()} />
<div className="flex flex-col items-center justify-center space-y-4">
<ImageIcon
className={`h-12 w-12 ${isDragActive ? 'text-primary' : 'text-gray-400'}`}
/>
<p className="text-sm text-gray-600">
{isDragActive
? 'Drop file here'
: 'Drag \'n\' drop an image, or click to select a file'
}
</p>
<p className="text-xs text-gray-500">
(Max 5MB, image files only)
</p>
</div>
</div>
</CardContent>
</Card>
</div>
}
{/* preview image */}
{preview && (
<Card className="overflow-y-scroll rounded-none">
<CardContent className="mt-2 mb-2">
<div className="w-fit aspect-square relative h-[100%]">
{
file?.type === "image/svg+xml" ? <object
data={preview}
type="image/svg+xml"
className="object-cover rounded-lg"
style={{ width: '100%', height: '100%' }}
>
Your browser does not support SVG, no preview available for SVG.
</object> :
<img
src={preview}
alt="Uploaded image"
className="object-cover rounded-lg overflow-hidden"
/>
}
<Separator className="my-4" />
<div className="grid grid-cols-1 gap-2 items-center pb-4">
<p className="text-sm text-gray-600 truncate">{file?.name}</p>
<Button
variant="destructive"
size="sm"
onClick={removeFile}
>
<Trash2 className="mr-2 h-4 w-4" />
Remove
</Button>
</div>
</div>
</CardContent>
</Card>
)}
</div>
</TabsContent>
{/* Effects */}
<TabsContent value="effects">
<ImageCustomization />
</TabsContent>
</Tabs>
</CardContent>
</Card>
)
}
export default UploadImage