import React, { useState, useCallback, useEffect } from 'react' import { Box, Container, Typography, Paper, Button, Slider, Stack, Grid, Card, CardContent, LinearProgress, IconButton, Tooltip, useTheme, ToggleButton, ToggleButtonGroup, Select, MenuItem, FormControl } from '@mui/material' import { useDropzone } from 'react-dropzone' import imageCompression from 'browser-image-compression' import { toast } from 'react-toastify' import { useTranslation } from 'react-i18next' import CloudUploadIcon from '@mui/icons-material/CloudUpload' import DownloadIcon from '@mui/icons-material/Download' import DeleteIcon from '@mui/icons-material/Delete' import InfoIcon from '@mui/icons-material/Info' import CompareArrowsIcon from '@mui/icons-material/CompareArrows' import LightModeIcon from '@mui/icons-material/LightMode' import DarkModeIcon from '@mui/icons-material/DarkMode' import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' import LanguageIcon from '@mui/icons-material/Language' import ReactMarkdown from 'react-markdown' import './i18n' const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB const ACCEPTED_TYPES = { 'image/jpeg': ['.jpg', '.jpeg'], 'image/png': ['.png'], 'image/gif': ['.gif'], 'image/svg+xml': ['.svg'], 'image/webp': ['.webp'] } const COMPRESSION_OPTIONS = [ { value: 0.1, label: '10%' }, // 10% quality = 90% compression { value: 0.25, label: '25%' }, // 25% quality = 75% compression { value: 0.5, label: '50%' }, // 50% quality = 50% compression { value: 0.75, label: '75%' }, // 75% quality = 25% compression { value: 0.9, label: '90%' }, // 90% quality = 10% compression ] // Add a helper component for markdown text const MarkdownText = ({ children, ...props }) => ( ( ), p: ({ node, ...props }) => }} {...props} > {children} ) function App({ mode, setMode }) { const { t, i18n } = useTranslation() const theme = useTheme() const [files, setFiles] = useState([]) const [compressionLevel, setCompressionLevel] = useState(0.5) const [isCompressing, setIsCompressing] = useState(false) const [originalSizes, setOriginalSizes] = useState({}) const [autoDownload, setAutoDownload] = useState(true) const [originalFiles, setOriginalFiles] = useState([]) const [compressionProgress, setCompressionProgress] = useState({}) const [language, setLanguage] = useState(i18n.language || 'en') const [isLoading, setIsLoading] = useState(false) const [compressionStats, setCompressionStats] = useState({ totalSize: 0, savedSize: 0, totalFiles: 0 }) const [previewUrls, setPreviewUrls] = useState({}) const [errors, setErrors] = useState({}) const languages = [ { code: 'en', name: 'English' }, { code: 'es', name: 'Español' }, { code: 'fr', name: 'Français' }, { code: 'hi', name: 'हिंदी' }, { code: 'zh', name: '中文' }, { code: 'ja', name: '日本語' }, { code: 'ar', name: 'العربية' } ] const handleLanguageChange = (event) => { const newLang = event.target.value setLanguage(newLang) i18n.changeLanguage(newLang) } const onDrop = useCallback((acceptedFiles) => { const validFiles = acceptedFiles.filter(file => file.size <= MAX_FILE_SIZE) if (validFiles.length !== acceptedFiles.length) { toast.error('Some files were too large (max 10MB)') } setFiles(validFiles) setOriginalFiles(validFiles) setErrors({}) // Store original sizes const sizes = {} validFiles.forEach(file => { sizes[file.name] = file.size }) setOriginalSizes(sizes) // Create preview URLs const previews = {} validFiles.forEach(file => { previews[file.name] = URL.createObjectURL(file) }) setPreviewUrls(previews) }, []) const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: ACCEPTED_TYPES, maxSize: MAX_FILE_SIZE, multiple: true }) // Cleanup preview URLs on unmount useEffect(() => { return () => { Object.values(previewUrls).forEach(url => URL.revokeObjectURL(url)) } }, [previewUrls]) const compressImage = async (file) => { const quality = 1 - compressionLevel const options = { maxSizeMB: 1, maxWidthOrHeight: 1920, useWebWorker: true, quality: quality, fileType: file.type, initialQuality: quality, alwaysKeepResolution: true, maxIteration: 5, exifOrientation: -1, checkOrientation: true, preserveExif: false, progressive: true, chromaSubsampling: '4:2:0', stripMetadata: true, optimization: true, progressiveLoading: true, interlacing: true, qualityBasedCompression: true, sizeBasedCompression: true, formatBasedCompression: true, contentBasedCompression: true, perceptualCompression: true, losslessCompression: true, lossyCompression: true, hybridCompression: true, adaptiveCompression: true, dynamicCompression: true, intelligentCompression: true, smartCompression: true, optimalCompression: true, maximumCompression: true, minimumCompression: true, balancedCompression: true, } try { const compressedFile = await imageCompression(file, options) return compressedFile } catch (error) { console.error('Error compressing image:', error) throw error } } const handleCompressionChange = (_, value) => { if (value !== null) { setCompressionLevel(value) } } const handleSliderChange = (_, value) => { setCompressionLevel(value) } const handleCompress = async () => { if (files.length === 0) { toast.error('Please select files to compress') return } setIsCompressing(true) setIsLoading(true) setCompressionProgress({}) setErrors({}) try { const compressedFiles = await Promise.all( originalFiles.map(async (file, index) => { try { setCompressionProgress(prev => ({ ...prev, [index]: 0 })) const compressed = await compressImage(file) setCompressionProgress(prev => ({ ...prev, [index]: 100 })) return compressed } catch (error) { setErrors(prev => ({ ...prev, [file.name]: 'Failed to compress image. Please try again.' })) return null } }) ) const validCompressedFiles = compressedFiles.filter(file => file !== null) if (validCompressedFiles.length === 0) { toast.error('No files were compressed successfully') return } setFiles(validCompressedFiles) // Calculate compression stats const totalOriginalSize = originalFiles.reduce((acc, file) => acc + file.size, 0) const totalCompressedSize = validCompressedFiles.reduce((acc, file) => acc + file.size, 0) setCompressionStats({ totalSize: totalOriginalSize, savedSize: totalOriginalSize - totalCompressedSize, totalFiles: validCompressedFiles.length }) // Auto-download if enabled if (autoDownload) { validCompressedFiles.forEach((file, index) => { const url = URL.createObjectURL(file) const link = document.createElement('a') const fileName = file.name.replace(/\.[^/.]+$/, '') const fileExtension = file.name.split('.').pop() const newFileName = `${fileName}.usingfreecompressor.${fileExtension}` link.href = url link.download = newFileName document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) }) } toast.success(`Successfully compressed ${validCompressedFiles.length} images!`) } catch (error) { console.error('Compression error:', error) toast.error('Error compressing images') } finally { setIsCompressing(false) setIsLoading(false) setCompressionProgress({}) } } // Memoize expensive calculations const calculateCompressionStats = useCallback((originalFiles, compressedFiles) => { const totalOriginalSize = originalFiles.reduce((acc, file) => acc + file.size, 0) const totalCompressedSize = compressedFiles.reduce((acc, file) => acc + file.size, 0) return { totalSize: totalOriginalSize, savedSize: totalOriginalSize - totalCompressedSize, totalFiles: compressedFiles.length } }, []) // Optimize cleanup useEffect(() => { const cleanup = () => { const urls = [...Object.values(previewUrls)] urls.forEach(url => { try { URL.revokeObjectURL(url) } catch (error) { console.warn('Failed to revoke URL:', error) } }) } window.addEventListener('beforeunload', cleanup) return () => { cleanup() window.removeEventListener('beforeunload', cleanup) } }, [previewUrls]) const handleDownload = (file, index) => { const url = URL.createObjectURL(file) const link = document.createElement('a') link.href = url link.download = `compressed_${index + 1}_${file.name}` document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } const handleDelete = (index) => { const file = files[index] if (previewUrls[file.name]) { URL.revokeObjectURL(previewUrls[file.name]) } setFiles(files.filter((_, i) => i !== index)) setOriginalFiles(originalFiles.filter((_, i) => i !== index)) setErrors(prev => { const newErrors = { ...prev } delete newErrors[file.name] return newErrors }) } const formatFileSize = (bytes) => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } const calculateReduction = (originalSize, currentSize) => { return ((originalSize - currentSize) / originalSize * 100).toFixed(1) } return ( {/* Header with Theme and Language Controls */} Kreative Logo setMode(mode === 'light' ? 'dark' : 'light')} sx={{ bgcolor: mode === 'light' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(59, 130, 246, 0.1)', '&:hover': { bgcolor: mode === 'light' ? 'rgba(239, 68, 68, 0.2)' : 'rgba(59, 130, 246, 0.2)', } }} > {mode === 'light' ? : } {t('title')} {t('subtitle')} {files.length > 0 && ( <> {t('quickCompression')} {COMPRESSION_OPTIONS.map((option) => ( {option.label} ))} {t('fineTuneCompression')} `${Math.round(value * 100)}%`} sx={{ mb: 4, '& .MuiSlider-markLabel': { color: mode === 'light' ? 'text.secondary' : 'text.primary', }, '& .MuiSlider-valueLabel': { bgcolor: mode === 'light' ? 'primary.main' : 'secondary.main', color: '#fff', } }} /> )} {files.length > 0 && ( {compressionStats.totalFiles > 0 && ( {t('compressionSummary')} {t('totalFiles')} {compressionStats.totalFiles} {t('totalSizeSaved')} {formatFileSize(compressionStats.savedSize)} {t('averageReduction')} {((compressionStats.savedSize / compressionStats.totalSize) * 100).toFixed(1)}% )} 0 ? 8 : 12}> {t('selectedFiles')} {files.map((file, index) => ( {compressionProgress[index] !== undefined && ( )} {previewUrls[file.name] && ( {file.name} )} {file.name} {errors[file.name] && ( {errors[file.name]} )} {t('original')}: {formatFileSize(originalSizes[file.name])} {t('compressed')}: {formatFileSize(file.size)} {calculateReduction(originalSizes[file.name], file.size)}% {t('smaller')} {compressionProgress[index] === 100 && ( )} ))} )} {/* SEO Content Section */} {t('howToTitle')} {[ 'compression', 'whyChoose', 'howItWorks', 'safety', 'features', 'usage' ].map((section) => ( {t(`sections.${section}.title`)} {t(`sections.${section}.description`)} {t(`sections.${section}.features`, { returnObjects: true }).map((feature, idx) => ( {feature} ))} ))} {/* Footer with Copyright */} {t('footer', { year: new Date().getFullYear() })} ) } export default App