Skip to content
Extraits de code Groupes Projets
Valider 4e679ebe rédigé par salaheddine zidani's avatar salaheddine zidani
Parcourir les fichiers

add pop-up to crop image

parent ccfd84bb
Branches
1 requête de fusion!125MYD-312/ add pop-up to crop image
Pipeline #2403 réussi avec l'étape
in 4 minutes et 27 secondes
import React, { useState, useCallback, useEffect } from 'react';
import Cropper from 'react-easy-crop';
import { Modal, Button, Typography, Grid, Slider, Stack, ButtonGroup, FormControl } from '@mui/material';
import { IMedia } from '@/shared/types/media';
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
import ZoomInIcon from '@mui/icons-material/ZoomIn';
import RotateLeftIcon from '@mui/icons-material/RotateLeft';
import RotateRightIcon from '@mui/icons-material/RotateRight';
import { Area } from 'react-easy-crop';
import { getCroppedImg } from './cropImage';
type ImageEditorProps = {
open: boolean;
onClose: () => void;
image: IMedia | undefined;
setEditedImage: (image: File | null) => void;
};
const aspectRatios = [
{ value: 4 / 3, label: '4:3' },
{ value: 16 / 9, label: '16:9' },
{ value: 1, label: '1:1' },
{ value: 3 / 2, label: '3:2' },
{ value: undefined, label: 'Free' },
];
const EditImageModal: React.FC<ImageEditorProps> = ({ open, onClose, image, setEditedImage }) => {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const [rotation, setRotation] = useState(0);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
const [imageUrl, setImageUrl] = useState('');
const [aspectRatio, setAspectRatio] = useState<number | undefined>(4 / 3);
const [originalAspectRatio, setOriginalAspectRatio] = useState<number | undefined>(undefined);
const [uploadedImage, setUploadedImage] = useState<string | null>(null);
useEffect(() => {
const extractUrl = (url: string) => {
const match = url.match(/Optional\[(.*)\]/);
return match ? match[1] : url;
};
if (image?.url && !uploadedImage) {
const imgUrl = extractUrl(image.url);
setImageUrl(imgUrl);
const img = new Image();
img.src = imgUrl;
img.onload = () => {
setOriginalAspectRatio(img.width / img.height);
};
}
}, [image, uploadedImage]);
const onCropComplete = useCallback((croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedAreaPixels(croppedAreaPixels);
}, []);
const handleSave = useCallback(async () => {
if (croppedAreaPixels && imageUrl) {
try {
const croppedImage = await getCroppedImg(imageUrl, croppedAreaPixels, rotation);
if (croppedImage) {
const file = new File([croppedImage], 'editedImage.png', { type: 'image/png' });
setEditedImage(file);
onClose();
}
} catch (e) {
console.error(e);
}
}
}, [croppedAreaPixels, rotation, imageUrl, onClose, setEditedImage]);
const handleAspectRatioChange = (newRatio: number | undefined) => {
setAspectRatio(newRatio);
};
const handleZoomChange = (value: number) => {
setZoom(Math.min(Math.max(value, 1), 3)); // Ensure the zoom value stays between 1 and 3
};
const handleRotationChange = (value: number) => {
setRotation(((value % 360) + 360) % 360); // Ensure the rotation value stays between 0 and 360
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setUploadedImage(reader.result as string);
setImageUrl(reader.result as string);
const img = new Image();
img.src = reader.result as string;
img.onload = () => {
setOriginalAspectRatio(img.width / img.height);
};
};
reader.readAsDataURL(file);
}
};
return (
<Modal open={open} onClose={onClose}>
<Grid container justifyContent="center" alignItems="center" style={{ width: '65%', maxWidth: '70%', position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'white', padding: '20px', borderRadius: '15px' }}>
<Grid item xs={12} sx={{ mb: 3 }}>
<Typography variant="h5" align="center" gutterBottom>
Modifier image
</Typography>
</Grid>
<Grid item xs={12} style={{ height: 400, position: 'relative' }}>
{imageUrl && (
<Cropper
image={imageUrl}
crop={crop}
zoom={zoom}
rotation={rotation}
aspect={aspectRatio}
cropShape="rect"
showGrid={false}
onCropChange={setCrop}
onZoomChange={setZoom}
onRotationChange={setRotation}
onCropComplete={onCropComplete}
/>
)}
</Grid>
<Grid item xs={12} style={{ marginTop: '20px' }}>
<Stack spacing={2}>
<FormControl fullWidth>
<ButtonGroup variant="outlined" aria-label="outlined button group">
{aspectRatios.map((ratio) => (
<Button
key={ratio.label}
onClick={() => handleAspectRatioChange(ratio.value)}
variant={aspectRatio === ratio.value ? 'contained' : 'outlined'}
>
{ratio.label}
</Button>
))}
{originalAspectRatio && (
<Button
onClick={() => handleAspectRatioChange(originalAspectRatio)}
variant={aspectRatio === originalAspectRatio ? 'contained' : 'outlined'}
>
Original ({originalAspectRatio.toFixed(2)})
</Button>
)}
</ButtonGroup>
</FormControl>
<Stack direction="row" alignItems="center" spacing={2}>
<Button variant="outlined" color="success" onClick={() => handleZoomChange(zoom - 0.1)} disabled={zoom <= 1}><ZoomOutIcon /></Button>
<Slider value={zoom} min={1} max={3} step={0.1} onChange={(e, value) => handleZoomChange(value as number)} />
<Button variant="outlined" color="success" onClick={() => handleZoomChange(zoom + 0.1)} disabled={zoom >= 3}><ZoomInIcon /></Button>
</Stack>
<Stack direction="row" alignItems="center" spacing={2}>
<Button variant="outlined" color="success" onClick={() => handleRotationChange(rotation - 10)} disabled={rotation <= 0}><RotateLeftIcon /></Button>
<Slider value={rotation} min={0} max={360} step={1} onChange={(e, value) => handleRotationChange(value as number)} />
<Button variant="outlined" color="success" onClick={() => handleRotationChange(rotation + 10)} disabled={rotation >= 360}><RotateRightIcon /></Button>
</Stack>
<input
accept="image/*"
type="file"
onChange={handleFileChange}
style={{ display: 'none' }}
id="file-upload"
/>
<label htmlFor="file-upload">
<Button variant="outlined" component="span">
Upload New Image
</Button>
</label>
</Stack>
</Grid>
<Grid item xs={12} style={{ marginTop: '20px' }}>
<Grid container justifyContent="center">
<Button variant="outlined" onClick={onClose} style={{ marginRight: '10px' }}>Cancel</Button>
<Button variant="contained" onClick={handleSave}>Save</Button>
</Grid>
</Grid>
</Grid>
</Modal>
);
};
export default EditImageModal;
import { Area } from 'react-easy-crop';
export const getCroppedImg = (imageSrc: string, pixelCrop: Area, rotation = 0): Promise<Blob | null> => {
const createImage = (url: string): Promise<HTMLImageElement> =>
new Promise((resolve, reject) => {
const image = new Image();
image.setAttribute('crossOrigin', 'anonymous');
image.onload = () => resolve(image);
image.onerror = (error) => reject(error);
image.src = url;
});
const getRadianAngle = (degreeValue: number) => (degreeValue * Math.PI) / 180;
const rotateSize = (width: number, height: number, rotation: number) => {
const rotRad = getRadianAngle(rotation);
return {
width:
Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
height:
Math.abs(Math.cos(rotRad) * height) + Math.abs(Math.sin(rotRad) * width),
};
};
return createImage(imageSrc).then((image) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
image.width,
image.height,
rotation
);
canvas.width = bBoxWidth;
canvas.height = bBoxHeight;
ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.drawImage(
image,
-image.width / 2,
-image.height / 2
);
const data = ctx.getImageData(0, 0, bBoxWidth, bBoxHeight);
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
ctx.putImageData(
data,
-pixelCrop.x,
-pixelCrop.y
);
return new Promise<Blob | null>((resolve) => {
canvas.toBlob((blob) => {
resolve(blob);
}, 'image/png');
});
});
};
0% ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter