mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-20 06:29:32 +02:00
feat: crop png
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Box, useTheme } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import ReactCrop, { Crop, PixelCrop } from 'react-image-crop';
|
||||
import 'react-image-crop/dist/ReactCrop.css';
|
||||
import InputHeader from '../InputHeader';
|
||||
import InputFooter from './InputFooter';
|
||||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
@@ -12,18 +14,57 @@ interface ToolFileInputProps {
|
||||
onChange: (file: File) => void;
|
||||
accept: string[];
|
||||
title?: string;
|
||||
showCropOverlay?: boolean;
|
||||
cropShape?: 'rectangular' | 'circular';
|
||||
cropPosition?: { x: number; y: number };
|
||||
cropSize?: { width: number; height: number };
|
||||
onCropChange?: (
|
||||
position: { x: number; y: number },
|
||||
size: { width: number; height: number }
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function ToolFileInput({
|
||||
value,
|
||||
onChange,
|
||||
accept,
|
||||
title = 'File'
|
||||
title = 'File',
|
||||
showCropOverlay = false,
|
||||
cropShape = 'rectangular',
|
||||
cropPosition = { x: 0, y: 0 },
|
||||
cropSize = { width: 100, height: 100 },
|
||||
onCropChange
|
||||
}: ToolFileInputProps) {
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const imageRef = useRef<HTMLImageElement>(null);
|
||||
const [imgWidth, setImgWidth] = useState(0);
|
||||
const [imgHeight, setImgHeight] = useState(0);
|
||||
|
||||
// Convert position and size to crop format used by ReactCrop
|
||||
const [crop, setCrop] = useState<Crop>({
|
||||
unit: 'px',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
});
|
||||
|
||||
const RATIO = imageRef.current ? imgWidth / imageRef.current.width : 1;
|
||||
|
||||
useEffect(() => {
|
||||
if (imgWidth && imgHeight) {
|
||||
setCrop({
|
||||
unit: 'px',
|
||||
x: cropPosition.x / RATIO,
|
||||
y: cropPosition.y / RATIO,
|
||||
width: cropSize.width / RATIO,
|
||||
height: cropSize.height / RATIO
|
||||
});
|
||||
}
|
||||
}, [cropPosition, cropSize, imgWidth, imgHeight]);
|
||||
|
||||
const handleCopy = () => {
|
||||
if (value) {
|
||||
@@ -38,14 +79,16 @@ export default function ToolFileInput({
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePaste = (event: ClipboardEvent) => {
|
||||
const clipboardItems = event.clipboardData?.items ?? [];
|
||||
const item = clipboardItems[0];
|
||||
if (item.type.includes('image')) {
|
||||
if (item && item.type.includes('image')) {
|
||||
const file = item.getAsFile();
|
||||
onChange(file!);
|
||||
if (file) onChange(file);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
const objectUrl = URL.createObjectURL(value);
|
||||
@@ -55,6 +98,8 @@ export default function ToolFileInput({
|
||||
return () => URL.revokeObjectURL(objectUrl);
|
||||
} else {
|
||||
setPreview(null);
|
||||
setImgWidth(0);
|
||||
setImgHeight(0);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
@@ -62,10 +107,54 @@ export default function ToolFileInput({
|
||||
const file = event.target.files?.[0];
|
||||
if (file) onChange(file);
|
||||
};
|
||||
|
||||
const handleImportClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
// Handle image load to set dimensions
|
||||
const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const { naturalWidth: width, naturalHeight: height } = e.currentTarget;
|
||||
setImgWidth(width);
|
||||
setImgHeight(height);
|
||||
|
||||
// Initialize crop with a centered default crop if needed
|
||||
if (!crop.width && !crop.height && onCropChange) {
|
||||
const initialCrop: Crop = {
|
||||
unit: 'px',
|
||||
x: Math.floor(width / 4),
|
||||
y: Math.floor(height / 4),
|
||||
width: Math.floor(width / 2),
|
||||
height: Math.floor(height / 2)
|
||||
};
|
||||
|
||||
setCrop(initialCrop);
|
||||
|
||||
// Notify parent component of initial crop
|
||||
onCropChange(
|
||||
{ x: initialCrop.x, y: initialCrop.y },
|
||||
{ width: initialCrop.width, height: initialCrop.height }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle crop changes from react-image-crop
|
||||
const handleCropChange = (newCrop: Crop) => {
|
||||
setCrop(newCrop);
|
||||
};
|
||||
|
||||
const handleCropComplete = (crop: PixelCrop) => {
|
||||
if (onCropChange) {
|
||||
onCropChange(
|
||||
{ x: Math.round(crop.x * RATIO), y: Math.round(crop.y * RATIO) },
|
||||
{
|
||||
width: Math.round(crop.width * RATIO),
|
||||
height: Math.round(crop.height * RATIO)
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('paste', handlePaste);
|
||||
|
||||
@@ -84,25 +173,48 @@ export default function ToolFileInput({
|
||||
border: preview ? 0 : 1,
|
||||
borderRadius: 2,
|
||||
boxShadow: '5',
|
||||
bgcolor: 'white'
|
||||
bgcolor: 'white',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{preview ? (
|
||||
<Box
|
||||
width={'100%'}
|
||||
height={'100%'}
|
||||
width="100%"
|
||||
height="100%"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundImage: `url(${greyPattern})`
|
||||
backgroundImage: `url(${greyPattern})`,
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={preview}
|
||||
alt="Preview"
|
||||
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
|
||||
/>
|
||||
{showCropOverlay ? (
|
||||
<ReactCrop
|
||||
crop={crop}
|
||||
onChange={handleCropChange}
|
||||
onComplete={handleCropComplete}
|
||||
circularCrop={cropShape === 'circular'}
|
||||
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
|
||||
>
|
||||
<img
|
||||
ref={imageRef}
|
||||
src={preview}
|
||||
alt="Preview"
|
||||
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
|
||||
onLoad={onImageLoad}
|
||||
/>
|
||||
</ReactCrop>
|
||||
) : (
|
||||
<img
|
||||
ref={imageRef}
|
||||
src={preview}
|
||||
alt="Preview"
|
||||
style={{ maxWidth: '100%', maxHeight: globalInputHeight }}
|
||||
onLoad={onImageLoad}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
|
Reference in New Issue
Block a user