288 lines
14 KiB
JavaScript
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
|
|
|
|
|