~firefoxreact-appqfsclientsrccomponents
3 itemsDownload ./*

..
CodeDisplay.jsx
FileList.jsx
TextShare.jsx


componentsFileList.jsx
11 KB• 6•  5 days ago•  DownloadRawClose
5 days ago•  6

{}
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 (
    <div>
      <h2 className="text-xl font-semibold mb-2">Files</h2>
      <input
        type="file"
        multiple
        ref={fileInputRef}
        onChange={handleFileSelect}
        className="mb-2"
      />
      <ul className="space-y-2">
        {files.map((file, index) => {
          const buttonClass =
            downloadStates[file.name]?.status === 'downloading'
              ? 'bg-yellow-500 text-black'
              : downloadStates[file.name]?.status === 'completed'
              ? 'bg-emerald-500 text-white'
              : 'bg-purple-900 text-pink-100';
          return (
            <li key={index} className="flex items-center space-x-2">
              {file.thumbnail && (
                <img
                  //src={getThumbnailUrl(file) || 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'} // 1x1 transparent GIF
                  src={getThumbnailUrl(file) || getFileIcon(file.name)}
                  alt={`${file.name} preview`}
                  className="w-16 h-16 object-cover mr-2"
                  onError={() => {
                    console.log(`Thumbnail failed to load for ${file.name}, requesting...`);
                  }}
                />
              ) || (
                <img
                  src={getFileIcon(file.name)}
                  alt="file icon"
                  className="w-6 h-6"
                  onError={(e) => (e.target.style.display = 'none')}
                />
              )} 
              <span>({formatFileSize(file.size)})</span>
              {file.peerId !== socketId && (
                <button
                  onClick={() => handleDownload(file)}
                  className={`px-4 py-2 rounded ${
                    downloadStates[file.name]?.status === 'saved'
                      ? 'bg-green-500 text-white'
                      : downloadStates[file.name]?.status === 'downloading'
                      ? 'bg-yellow-500 text-black'
                      : 'bg-purple-600 text-darkPurple hover:bg-purple-500'
                  }`}
                >
                  {downloadStates[file.name]?.status === 'saved'
                    ? 'Saved ✓'
                    : downloadStates[file.name]?.status === 'downloading'
                    ? `Cancel ${(downloadStates[file.name]?.progress || 0).toFixed(2)}%`
                    : 'Download'}
                </button>
              )}
              {file.peerId === socketId && (
                <>
                  <button
                    onClick={() => handleDeleteFile(file.name)}
                    className="bg-rose-800 text-darkPurple px-4 py-2 rounded hover:bg-rose-700"
                  >
                    Delete
                  </button> 
                </>
              )}
              {downloadCounts[file.name] > 0 && (
                <span>{downloadCounts[file.name]} downloading</span>
              )}
              <span>{file.name}</span>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

export default FileList;

Top
©twily.info 2013 - 2025
twily at twily dot info



2 419 836 visits
... ^ v