canvas-backend/src/components/EachComponent/Customization/AddImageIntoShape.jsx

353 lines
16 KiB
JavaScript

import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext';
import CanvasContext from '@/components/Context/canvasContext/CanvasContext';
import { useContext, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { fabric } from 'fabric';
import Resizer from "react-image-file-resizer";
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Slider } from '@/components/ui/slider';
import { HardDriveUpload, ImagePlus, Upload, Crop, RotateCw, FileType, ChevronUp, ChevronDown } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import CollapsibleComponent from './CollapsibleComponent';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
const features = [
{ icon: Upload, title: 'Auto-Resize', description: 'Images larger than 1080px are automatically resized' },
{ icon: Crop, title: 'Custom Dimensions', description: 'Set your preferred resize dimensions' },
{ icon: RotateCw, title: 'Quality & Rotation', description: 'Adjust image quality and rotation as needed' },
{ icon: FileType, title: 'Format Conversion', description: 'Convert between various image formats' },
]
const AddImageIntoShape = () => {
const { canvas } = useContext(CanvasContext);
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
const [errorMessage, setErrorMessage] = useState("");
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 [isOpen, setIsOpen] = useState(false);
const handleResize = (file, callback) => {
Resizer.imageFileResizer(
file,
width,
height,
format,
quality,
parseInt(rotation),
(resizedFile) => {
callback(resizedFile); // Pass the resized file to the callback
},
'file' // Output type
);
};
const fileHandler = (e) => {
const file = e.target.files[0];
if (!file) {
setErrorMessage("No file selected.");
return;
}
// Check if the file is an SVG
if (file.type === "image/svg+xml") {
// Add SVG directly to canvas without compression
const blobUrl = URL.createObjectURL(file);
const imgElement = new Image();
imgElement.src = blobUrl;
imgElement.onload = () => {
handleImageInsert(imgElement); // Insert the image without resizing
URL.revokeObjectURL(blobUrl);
clearFileInput();
};
imgElement.onerror = () => {
console.error("Failed to load SVG.");
setErrorMessage("Failed to load SVG.");
URL.revokeObjectURL(blobUrl);
clearFileInput();
};
return;
}
// Handle raster images (JPEG, PNG, etc.)
const imgElement = new Image();
const blobUrl = URL.createObjectURL(file);
imgElement.src = blobUrl;
imgElement.onload = () => {
// If the width is greater than 1080px, compress the image
if (imgElement.width > 1080) {
handleResize(file, (compressedFile) => {
const compressedBlobUrl = URL.createObjectURL(compressedFile);
const compressedImg = new Image();
compressedImg.src = compressedBlobUrl;
compressedImg.onload = () => {
handleImageInsert(compressedImg); // Insert the resized image
URL.revokeObjectURL(compressedBlobUrl); // Clean up
clearFileInput();
};
compressedImg.onerror = () => {
console.error("Failed to load compressed image.");
setErrorMessage("Failed to load compressed image.");
URL.revokeObjectURL(compressedBlobUrl);
clearFileInput();
};
});
} else {
handleImageInsert(imgElement); // Insert the original image if no resizing needed
clearFileInput();
}
URL.revokeObjectURL(blobUrl); // Clean up
clearFileInput();
};
imgElement.onerror = () => {
console.error("Failed to load image.");
setErrorMessage("Failed to load image.");
URL.revokeObjectURL(blobUrl);
clearFileInput();
};
};
const clearFileInput = () => {
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
const handleImageInsert = (img) => {
if (!activeObject) {
setErrorMessage("No active object selected!");
return;
}
// Ensure absolute positioning for the clipPath
activeObject.set({
isClipPath: true, // Custom property
absolutePositioned: true,
});
// Calculate scale factors based on clip object size
let scaleX = activeObject.width / img.width;
let scaleY = activeObject.height / img.height;
if (activeObject?.width < 100) {
scaleX = 0.2;
}
if (activeObject.height < 100) {
scaleY = 0.2;
}
// Create a fabric image object with scaling and clipPath
const fabricImage = new fabric.Image(img, {
scaleX: scaleX,
scaleY: scaleY,
left: activeObject.left,
top: activeObject.top,
clipPath: activeObject, // Apply clipPath to the image
originX: activeObject.originX, // Match origin point
originY: activeObject.originY // Match origin point
});
// Adjust position based on the clipPath's transformations
fabricImage.set({
left: activeObject.left,
top: activeObject.top,
angle: activeObject.angle // Match rotation if any
});
canvas.add(fabricImage);
canvas.setActiveObject(fabricImage);
setActiveObject(fabricImage);
canvas.renderAll();
};
// canvas.remove(activeObject);
const content = () => {
return (
<div>
{/* <Card className="my-2">
<CardContent className="p-0">
<Alert>
<AlertTitle className="text-lg font-semibold flex items-center gap-1 flex-wrap">Image Insertion <ImagePlus className="h-5 w-5" /></AlertTitle>
<AlertDescription className="mt-1">
<p className="mb-1">
Insert and customize images within shapes. Adjust image position and clipping after insertion.
</p>
<h4 className="font-medium mb-1">Key Features:</h4>
<div className="grid grid-cols-1 gap-2 mt-2">
{features.map((feature, index) => (
<div
key={index}
className="flex items-start p-4 bg-white rounded-lg shadow-sm"
>
<feature.icon className="h-6 w-6 mr-3 flex-shrink-0 text-primary" />
<div>
<h3 className="font-semibold mb-1">{feature.title}</h3>
<p className="text-sm text-gray-600">{feature.description}</p>
</div>
</div>
))}
</div>
</AlertDescription>
</Alert>
</CardContent>
</Card> */}
<Card className="my-2">
<CardContent className="p-0">
<Alert>
<AlertTitle className="text-lg font-semibold flex items-center gap-1 flex-wrap">
Image Insertion <ImagePlus className="h-5 w-5" />
</AlertTitle>
<AlertDescription className="mt-1">
<p className="mb-1">
Insert and customize images within shapes. Adjust image position and clipping after insertion.
</p>
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild>
<Button variant="outline" className="w-full justify-between mt-2">
<span>Key Features</span>
{isOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="grid grid-cols-1 gap-2 mt-2">
{features.map((feature, index) => (
<div
key={index}
className="flex items-start p-4 bg-white rounded-lg shadow-sm"
>
<feature.icon className="h-6 w-6 mr-3 flex-shrink-0 text-primary" />
<div>
<h3 className="font-semibold mb-1">{feature.title}</h3>
<p className="text-sm text-gray-600">{feature.description}</p>
</div>
</div>
))}
</div>
</CollapsibleContent>
</Collapsible>
</AlertDescription>
</Alert>
</CardContent>
</Card>
{errorMessage && (
<p className="text-red-600 text-sm mt-2">{errorMessage}</p>
)}
<div className='flex flex-col w-[100%]'>
<div className="space-y-1">
{/* Width Slider */}
<div className="space-y-1">
<Label className="text-xs">Width: {width}px</Label>
<Slider
min={300}
value={[width]}
max={2000}
step={10}
onValueChange={(value) => setWidth(value[0])}
/>
</div>
{/* Height Slider */}
<div className="space-y-1">
<Label className="text-xs">Height: {height}px</Label>
<Slider
min={300}
value={[height]}
max={2000}
step={10}
onValueChange={(value) => setHeight(value[0])}
/>
</div>
{/* Quality Slider */}
{
format === "JPEG" &&
<div className="space-y-1">
<Label className="text-xs">Quality: {quality}%</Label>
<Slider
value={[quality]}
max={100}
step={1}
onValueChange={(value) => setQuality(value[0])}
/>
</div>
}
<div className='grid grid-cols-2 gap-1 items-center'>
{/* Rotation */}
<div>
<Label className="text-xs">Rotation: {rotation}°</Label>
<Select value={rotation} onValueChange={(value) => setRotation(value)}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select rotation in degree" />
</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>
{/* Format Dropdown */}
<div>
<Label className="text-xs">Format</Label>
<Select value={format} onValueChange={(value) => setFormat(value)}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select format" />
</SelectTrigger>
<SelectContent>
<SelectItem value="JPEG">JPEG</SelectItem>
<SelectItem value="PNG">PNG</SelectItem>
<SelectItem value="WEBP">WEBP</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<Button className="w-fit h-fit flex flex-wrap gap-1 relative py-4 my-4 mx-auto">
<HardDriveUpload className="cursor-pointer" />
Upload Image
<Input
className="cursor-pointer bg-white text-black absolute top-0 opacity-0"
type="file"
accept="image/*"
ref={fileInputRef}
onChange={fileHandler} />
</Button>
</div>
</div>
)
}
return (
<Card className="flex flex-col p-2">
<CollapsibleComponent text={"Insert Image"}>
{content()}
</CollapsibleComponent>
</Card>
);
};
export default AddImageIntoShape;