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'; import greyPattern from '@assets/grey-pattern.png'; import { globalInputHeight } from '../../config/uiConfig'; import Slider from 'rc-slider'; import 'rc-slider/assets/index.css'; interface ToolFileInputProps { value: File | null; 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; type?: 'image' | 'video' | 'audio'; // Video specific props showTrimControls?: boolean; onTrimChange?: (trimStart: number, trimEnd: number) => void; trimStart?: number; trimEnd?: number; } export default function ToolFileInput({ value, onChange, accept, title = 'File', showCropOverlay = false, cropShape = 'rectangular', cropPosition = { x: 0, y: 0 }, cropSize = { width: 100, height: 100 }, onCropChange, type = 'image', showTrimControls = false, onTrimChange, trimStart = 0, trimEnd = 100 }: ToolFileInputProps) { const [preview, setPreview] = useState(null); const theme = useTheme(); const { showSnackBar } = useContext(CustomSnackBarContext); const fileInputRef = useRef(null); const imageRef = useRef(null); const videoRef = useRef(null); const [imgWidth, setImgWidth] = useState(0); const [imgHeight, setImgHeight] = useState(0); const [videoDuration, setVideoDuration] = useState(0); // Convert position and size to crop format used by ReactCrop const [crop, setCrop] = useState({ 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) { const blob = new Blob([value], { type: value.type }); const clipboardItem = new ClipboardItem({ [value.type]: blob }); navigator.clipboard .write([clipboardItem]) .then(() => showSnackBar('File copied', 'success')) .catch((err) => { showSnackBar('Failed to copy: ' + err, 'error'); }); } }; useEffect(() => { if (value) { const objectUrl = URL.createObjectURL(value); setPreview(objectUrl); // Clean up memory when the component is unmounted or the file changes return () => URL.revokeObjectURL(objectUrl); } else { setPreview(null); setImgWidth(0); setImgHeight(0); } }, [value]); const handleFileChange = (event: React.ChangeEvent) => { 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) => { 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 video load to set duration const onVideoLoad = (e: React.SyntheticEvent) => { const duration = e.currentTarget.duration; setVideoDuration(duration); // Initialize trim with full duration if needed if (onTrimChange && trimStart === 0 && trimEnd === 100) { onTrimChange(0, duration); } }; 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) } ); } }; const handleTrimChange = (start: number, end: number) => { if (onTrimChange) { onTrimChange(start, end); } }; useEffect(() => { const handlePaste = (event: ClipboardEvent) => { const clipboardItems = event.clipboardData?.items ?? []; const item = clipboardItems[0]; if ( item && (item.type.includes('image') || item.type.includes('video')) ) { const file = item.getAsFile(); if (file) onChange(file); } }; window.addEventListener('paste', handlePaste); return () => { window.removeEventListener('paste', handlePaste); }; }, [onChange]); // Format seconds to MM:SS format const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes.toString().padStart(2, '0')}:${remainingSeconds .toString() .padStart(2, '0')}`; }; return ( {preview ? ( {type === 'image' && (showCropOverlay ? ( Preview ) : ( Preview ))} {type === 'video' && ( )} {type === 'audio' && ( ) : ( Click here to select a {type} from your device, press Ctrl+V to use a {type} from your clipboard, drag and drop a file from desktop )} ); }