import { useRef, useState, useEffect } from 'react'; function FileList({ files, setFiles, dataChannels, dataChannelsRef, useFallback, socket, code, socketId, localFilesRef, downloadStates, setDownloadStates, cancelDownload, cancelRequestsRef, downloadCounts, handleDeleteFile, SERVER_URL }) { const fileInputRef = useRef(null); const thumbnailUrlsRef = useRef({}); // New ref to cache thumbnail URLs const generateThumbnail = (file, maxSize = 256) => { return new Promise((resolve) => { if (!file.type.startsWith('image/')) { resolve(null); return; } const img = new Image(); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); img.onload = () => { const { width, height } = img; const scale = Math.min(maxSize / width, maxSize / height, 1); canvas.width = width * scale; canvas.height = height * scale; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob((blob) => { resolve(blob); }, 'image/jpeg', 0.7); // 70% quality JPEG }; img.src = URL.createObjectURL(file); }); }; const handleFileSelect = async () => { const inputFiles = fileInputRef.current.files; if (!inputFiles || inputFiles.length === 0) return; const fileList = Array.from(inputFiles).map(file => ({ name: file.name, size: file.size, peerId: socketId, thumbnail: file.type.startsWith('image/') ? true : false, })); if (useFallback) { const formData = new FormData(); Array.from(inputFiles).forEach(file => formData.append('files', file)); try { const response = await fetch(`${SERVER_URL}/api/upload/${code}`, { method: 'POST', body: formData, headers: { 'x-socket-id': socketId }, }); if (!response.ok) throw new Error('File upload failed'); console.log('Files uploaded to server:', fileList); } catch (err) { console.error('Error uploading files:', err); } } else { // Generate thumbnails for all image files const thumbnails = await Promise.all( Array.from(inputFiles).map(async (file) => ({ name: file.name, thumbnail: file.type.startsWith('image/') ? await generateThumbnail(file) : null, })) ); localFilesRef.current = { ...localFilesRef.current, ...Object.fromEntries(Array.from(inputFiles).map(file => [file.name, file])), ...Object.fromEntries( thumbnails .filter(({ thumbnail }) => thumbnail) .map(({ name, thumbnail }) => [`${name}_thumbnail`, thumbnail]) ), }; // Create and cache thumbnail URLs once here thumbnails.forEach(({ name, thumbnail }) => { if (thumbnail) { const url = URL.createObjectURL(thumbnail); thumbnailUrlsRef.current[name] = url; console.log(`Cached thumbnail URL for ${name}: ${url}`); } }); console.log(`Stored files in localFilesRef:`, Object.keys(localFilesRef.current)); // Emit file-list to all peers socket.emit('file-list', { code, files: fileList }); console.log('Shared file list:', fileList); } setFiles((prev) => [...prev, ...fileList]); fileInputRef.current.value = ''; }; const handleDownload = async (file) => { if (cancelRequestsRef.current.has(file.name)) { cancelRequestsRef.current.delete(file.name); console.log(`Cleared previous cancel for ${file.name}, starting new request to ${file.peerId}`); } if (downloadStates[file.name]?.status === 'downloading') { cancelDownload(file.name, file.peerId); console.log(`Canceling download for ${file.name}`); return; } setDownloadStates((prev) => ({ ...prev, [file.name]: { status: 'downloading', progress: 0, total: file.size, peerId: file.peerId }, })); console.log(`Starting download for ${file.name}, downloadStates:`, { ...downloadStates, [file.name]: { status: 'downloading', progress: 0 }}); if (useFallback) { socket.emit('download-start-fallback', { code, fileName: file.name }); const link = document.createElement('a'); link.href = `${SERVER_URL}/uploads/${file.path.split('/').pop()}`; link.download = file.name; link.click(); console.log(`Downloading file via server: ${file.name}`); setDownloadStates((prev) => ({ ...prev, [file.name]: { status: 'completed', progress: 100 }, })); socket.emit('download-end-fallback', { code, fileName: file.name }); } else if (file.peerId === socketId) { const localFile = localFilesRef.current[file.name] || files.find(f => f.name === file.name)?.file; if (localFile) { socket.emit('download-start-fallback', { code, fileName: file.name }); const url = URL.createObjectURL(localFile); const link = document.createElement('a'); link.href = url; link.download = file.name; link.click(); URL.revokeObjectURL(url); console.log(`Downloaded local file: ${file.name}`); setDownloadStates((prev) => ({ ...prev, [file.name]: { status: 'saved', progress: 100 }, })); socket.emit('download-end-fallback', { code, fileName: file.name }); } else { console.error(`Local file not found: ${file.name}`); } } else { if (dataChannels[file.peerId]?.readyState === 'open') { //socket.emit('request-file', { code, fileName: file.name, to: file.peerId }); dataChannelsRef.current[file.peerId].send(JSON.stringify({ type: 'request-file', fileName: file.name, })); console.log(`Requested file ${file.name} from ${file.peerId}`); } else { console.error(`Data channel not open for peer ${file.peerId}`); } } }; const getFileIcon = (name) => { const ext = name.split('.').pop().toLowerCase(); const iconMap = { exe: 'executable.png', bin: 'executable.png', dll: 'executable.png', jpg: 'picture.png', jpeg: 'picture.png', png: 'picture.png', gif: 'picture.png', mp3: 'audio.png', wav: 'audio.png', ogg: 'audio.png', mp4: 'video.png', mkv: 'video.png', avi: 'video.png', mov: 'video.png', wma: 'video.png', pdf: 'unknown.png', doc: 'unknown.png', docx: 'unknown.png', txt: 'text.png', log: 'text.png', bat: 'script.png', sh: 'script.png', html: 'html.png', xml: 'xml.png', zip: 'archive.png', gz: 'archive.png', bz: 'archive.png', }; return `/imgs/${iconMap[ext] || 'unknown.png'}`; }; const formatFileSize = (size) => { if (!size) return 'Unknown'; if (size < 1024) return `${size} B`; if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`; if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`; return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`; }; // Generate or retrieve thumbnail URL const getThumbnailUrl = (file) => { if (!file.thumbnail) { console.log(`No thumbnail flag for ${file.name}`); return null; } // Check if URL is already cached if (thumbnailUrlsRef.current[file.name]) { //console.log(`Returning cached thumbnail URL for ${file.name}: ${thumbnailUrlsRef.current[file.name]}`); return thumbnailUrlsRef.current[file.name]; } // Check if thumbnail exists in localFilesRef (local or remote) const thumbnail = localFilesRef.current[`${file.name}_thumbnail`]; if (thumbnail) { const url = URL.createObjectURL(thumbnail); thumbnailUrlsRef.current[file.name] = url; console.log(`Generated and cached thumbnail URL for ${file.name}: ${url}`); return url; } console.log(`No thumbnail available for ${file.name}`); return null; }; // Cleanup: Revoke all thumbnail URLs on unmount useEffect(() => { return () => { Object.values(thumbnailUrlsRef.current).forEach((url) => { if (url) URL.revokeObjectURL(url); }); thumbnailUrlsRef.current = {}; console.log('Revoked all thumbnail URLs on unmount'); }; }, []); return (

Files

); } export default FileList;