223 lines
6.4 KiB
React
223 lines
6.4 KiB
React
|
|
import React, { useState, useRef, useEffect } from "react";
|
||
|
|
import ReactCrop from "react-image-crop";
|
||
|
|
import "react-image-crop/dist/ReactCrop.css";
|
||
|
|
import { FiX, FiScissors } from "react-icons/fi";
|
||
|
|
|
||
|
|
const ResizableCropper = ({ imageSrc, onCropComplete, onCancel, onNext, currentIndex, total }) => {
|
||
|
|
const [crop, setCrop] = useState({ unit: "%", width: 80, height: 100, aspect: 4 / 5 });
|
||
|
|
const [completedCrop, setCompletedCrop] = useState(null);
|
||
|
|
const [screenSize, setScreenSize] = useState('desktop');
|
||
|
|
const imgRef = useRef(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const updateScreenSize = () => {
|
||
|
|
const width = window.innerWidth;
|
||
|
|
if (width <= 480) setScreenSize('mobile');
|
||
|
|
else if (width <= 768) setScreenSize('tablet');
|
||
|
|
else if (width <= 1024) setScreenSize('laptop');
|
||
|
|
else setScreenSize('desktop');
|
||
|
|
};
|
||
|
|
|
||
|
|
updateScreenSize();
|
||
|
|
window.addEventListener('resize', updateScreenSize);
|
||
|
|
return () => window.removeEventListener('resize', updateScreenSize);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// Convert crop to base64 image
|
||
|
|
const getCroppedImage = () => {
|
||
|
|
if (!imgRef.current || !completedCrop?.width || !completedCrop?.height) return null;
|
||
|
|
|
||
|
|
const canvas = document.createElement("canvas");
|
||
|
|
const scaleX = imgRef.current.naturalWidth / imgRef.current.width;
|
||
|
|
const scaleY = imgRef.current.naturalHeight / imgRef.current.height;
|
||
|
|
|
||
|
|
canvas.width = completedCrop.width * scaleX;
|
||
|
|
canvas.height = completedCrop.height * scaleY;
|
||
|
|
|
||
|
|
const ctx = canvas.getContext("2d");
|
||
|
|
ctx.drawImage(
|
||
|
|
imgRef.current,
|
||
|
|
completedCrop.x * scaleX,
|
||
|
|
completedCrop.y * scaleY,
|
||
|
|
completedCrop.width * scaleX,
|
||
|
|
completedCrop.height * scaleY,
|
||
|
|
0,
|
||
|
|
0,
|
||
|
|
completedCrop.width * scaleX,
|
||
|
|
completedCrop.height * scaleY
|
||
|
|
);
|
||
|
|
|
||
|
|
return canvas.toDataURL("image/jpeg");
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleNext = () => {
|
||
|
|
const cropped = getCroppedImage();
|
||
|
|
if (cropped) onCropComplete(cropped);
|
||
|
|
if (onNext) onNext();
|
||
|
|
};
|
||
|
|
|
||
|
|
const getResponsiveStyles = () => {
|
||
|
|
switch (screenSize) {
|
||
|
|
case 'mobile':
|
||
|
|
return {
|
||
|
|
containerPadding: "12px",
|
||
|
|
imagePadding: 8,
|
||
|
|
maxHeight: "250px",
|
||
|
|
buttonPadding: "14px 20px",
|
||
|
|
fontSize: 13,
|
||
|
|
iconSize: 16,
|
||
|
|
gap: 8,
|
||
|
|
direction: 'row',
|
||
|
|
borderRadius: 8
|
||
|
|
};
|
||
|
|
case 'tablet':
|
||
|
|
return {
|
||
|
|
containerPadding: "16px",
|
||
|
|
imagePadding: 12,
|
||
|
|
maxHeight: "320px",
|
||
|
|
buttonPadding: "12px 18px",
|
||
|
|
fontSize: 13,
|
||
|
|
iconSize: 16,
|
||
|
|
gap: 10,
|
||
|
|
direction: 'row',
|
||
|
|
borderRadius: 10
|
||
|
|
};
|
||
|
|
case 'laptop':
|
||
|
|
return {
|
||
|
|
containerPadding: "18px",
|
||
|
|
imagePadding: 14,
|
||
|
|
maxHeight: "380px",
|
||
|
|
buttonPadding: "12px 20px",
|
||
|
|
fontSize: 14,
|
||
|
|
iconSize: 16,
|
||
|
|
gap: 12,
|
||
|
|
direction: 'row',
|
||
|
|
borderRadius: 12
|
||
|
|
};
|
||
|
|
default:
|
||
|
|
return {
|
||
|
|
containerPadding: "20px",
|
||
|
|
imagePadding: 16,
|
||
|
|
maxHeight: "400px",
|
||
|
|
buttonPadding: "12px 20px",
|
||
|
|
fontSize: 14,
|
||
|
|
iconSize: 16,
|
||
|
|
gap: 12,
|
||
|
|
direction: 'row',
|
||
|
|
borderRadius: 12
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const styles = getResponsiveStyles();
|
||
|
|
|
||
|
|
const buttonStyle = {
|
||
|
|
padding: styles.buttonPadding,
|
||
|
|
border: "none",
|
||
|
|
borderRadius: 8,
|
||
|
|
cursor: "pointer",
|
||
|
|
fontSize: styles.fontSize,
|
||
|
|
fontWeight: 500,
|
||
|
|
transition: "all 0.3s ease",
|
||
|
|
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
||
|
|
display: "flex",
|
||
|
|
alignItems: "center",
|
||
|
|
gap: 6,
|
||
|
|
width: screenSize === 'mobile' ? '100%' : 'auto',
|
||
|
|
justifyContent: 'center',
|
||
|
|
minHeight: screenSize === 'mobile' ? '48px' : 'auto'
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={{
|
||
|
|
textAlign: "center",
|
||
|
|
marginTop: screenSize === 'mobile' ? 12 : 20,
|
||
|
|
padding: styles.containerPadding,
|
||
|
|
backgroundColor: "#f8f9fa",
|
||
|
|
borderRadius: styles.borderRadius,
|
||
|
|
margin: screenSize === 'mobile' ? '12px 8px' : '20px auto',
|
||
|
|
maxWidth: screenSize === 'mobile' ? '100%' : '500px'
|
||
|
|
}}>
|
||
|
|
{/* {screenSize !== 'mobile' && (
|
||
|
|
<div style={{ marginBottom: 16 }}>
|
||
|
|
<h3 style={{ margin: 0, color: "#333", fontSize: screenSize === 'tablet' ? 16 : 18 }}>
|
||
|
|
Crop Image {currentIndex + 1} of {total}
|
||
|
|
</h3>
|
||
|
|
</div>
|
||
|
|
)} */}
|
||
|
|
|
||
|
|
<div style={{
|
||
|
|
backgroundColor: "#fff",
|
||
|
|
padding: styles.imagePadding,
|
||
|
|
borderRadius: 8,
|
||
|
|
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||
|
|
overflow: 'hidden'
|
||
|
|
}}>
|
||
|
|
{screenSize === 'mobile' && (
|
||
|
|
<div style={{ marginBottom: 8, fontSize: 12, color: "#666" }}>
|
||
|
|
Image {currentIndex + 1} of {total}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<ReactCrop
|
||
|
|
crop={crop}
|
||
|
|
onChange={(newCrop) => setCrop(newCrop)}
|
||
|
|
onComplete={(newCrop) => setCompletedCrop(newCrop)}
|
||
|
|
keepSelection={true}
|
||
|
|
>
|
||
|
|
<img
|
||
|
|
ref={imgRef}
|
||
|
|
src={imageSrc}
|
||
|
|
alt="Preview"
|
||
|
|
style={{
|
||
|
|
maxWidth: "100%",
|
||
|
|
maxHeight: styles.maxHeight,
|
||
|
|
borderRadius: 4,
|
||
|
|
touchAction: 'none'
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</ReactCrop>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style={{
|
||
|
|
marginTop: screenSize === 'mobile' ? 16 : 20,
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: styles.direction,
|
||
|
|
justifyContent: "center",
|
||
|
|
gap: styles.gap,
|
||
|
|
padding: screenSize === 'mobile' ? '0 4px' : '0'
|
||
|
|
}}>
|
||
|
|
<button
|
||
|
|
onClick={onCancel}
|
||
|
|
style={{
|
||
|
|
...buttonStyle,
|
||
|
|
backgroundColor: "#6c757d",
|
||
|
|
color: "#fff",
|
||
|
|
fontFamily: "Poppins"
|
||
|
|
}}
|
||
|
|
onMouseOver={(e) => e.target.style.backgroundColor = "#5a6268"}
|
||
|
|
onMouseOut={(e) => e.target.style.backgroundColor = "#6c757d"}
|
||
|
|
>
|
||
|
|
<FiX size={styles.iconSize} />
|
||
|
|
{screenSize === 'mobile' ? 'Cancel' : 'Cancel All'}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleNext}
|
||
|
|
style={{
|
||
|
|
...buttonStyle,
|
||
|
|
backgroundColor: "#1890ff",
|
||
|
|
color: "#fff",
|
||
|
|
fontFamily: "Poppins"
|
||
|
|
}}
|
||
|
|
onMouseOver={(e) => e.target.style.backgroundColor = "#096dd9"}
|
||
|
|
onMouseOut={(e) => e.target.style.backgroundColor = "#1890ff"}
|
||
|
|
>
|
||
|
|
<FiScissors size={styles.iconSize} />
|
||
|
|
{screenSize === 'mobile' ? 'Crop' : 'Crop '}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default ResizableCropper;
|