Ctrl K
ComunidadGuías…

public/index.html

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>File Manager Server</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: {
              light: '#4895ef',
              DEFAULT: '#4361ee',
              dark: '#3f37c9',
            },
          },
        },
      },
    }
  </script>
</head>
<body class="bg-gray-50 text-gray-800 min-h-screen">
  <!-- Header -->
  <header class="bg-gradient-to-r from-primary-dark to-primary-light text-white py-6 px-4 shadow-md">
    <div class="max-w-5xl mx-auto">
      <div class="flex items-center justify-center">
        <i class="fas fa-file-alt text-2xl mr-3 opacity-90"></i>
        <h1 class="text-2xl font-semibold">File Manager Server</h1>
      </div>
      <p class="text-center text-white/80 mt-1">Upload and download files from any folder on your server</p>
      <div class="text-center mt-2">
        <a href="https://github.com/p32929/node_file_manager_server" target="_blank" rel="noopener noreferrer" class="inline-flex items-center text-white/90 hover:text-white text-sm bg-white/10 px-3 py-1 rounded-full transition-colors">
          <i class="fab fa-github mr-2"></i>
          <span>Source Code</span>
        </a>
      </div>
    </div>
  </header>

  <main class="max-w-5xl mx-auto p-4 md:p-6">
    <!-- Explorer card -->
    <div id="explorerCard" class="bg-white rounded-xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
      <div class="flex items-center justify-between bg-gray-50 px-4 py-3 border-b border-gray-100">
        <h2 class="text-lg font-medium text-gray-800">File Explorer</h2>
        <button 
          onclick="showHelp()" 
          class="text-gray-500 hover:text-gray-700 bg-white border border-gray-200 px-3 py-1 rounded-md text-sm transition-colors flex items-center gap-1.5 hover:bg-gray-50">
          <i class="fas fa-question-circle text-xs"></i>
          <span>Help</span>
        </button>
      </div>
      
      <div class="p-4 md:p-5">
        <!-- Path navigation -->
        <div id="breadcrumb" class="text-sm text-primary-dark mb-4 p-2 bg-gray-50 rounded-md overflow-x-auto whitespace-nowrap"></div>
        
        <!-- Folder listing -->
        <div id="folderContainer" class="min-h-[150px] border border-gray-100 rounded-lg p-4 bg-gray-50/50">
          <div class="flex items-center justify-center h-[120px] text-gray-500">
            <div class="flex items-center gap-2">
              <i class="fas fa-spinner fa-spin text-primary"></i>
              <span>Loading folders...</span>
            </div>
          </div>
        </div>
        
        <!-- Navigation buttons -->
        <div class="flex flex-wrap gap-2 mt-4">
          <button 
            onclick="goBack()" 
            class="bg-white text-gray-700 border border-gray-200 hover:bg-gray-50 px-4 py-2 rounded-lg flex items-center gap-1.5 transition-colors">
            <i class="fas fa-arrow-left text-xs"></i>
            <span>Back</span>
          </button>
          
          <button 
            onclick="goUp()" 
            class="bg-white text-gray-700 border border-gray-200 hover:bg-gray-50 px-4 py-2 rounded-lg flex items-center gap-1.5 transition-colors">
            <i class="fas fa-arrow-up text-xs"></i>
            <span>Up</span>
          </button>
          
          <button 
            onclick="selectCurrentFolder()" 
            class="bg-primary hover:bg-primary-dark text-white px-4 py-2 rounded-lg flex items-center gap-1.5 transition-colors ml-auto">
            <i class="fas fa-check text-xs"></i>
            <span>Select This Folder</span>
          </button>
        </div>
      </div>
    </div>

    <!-- Upload card -->
    <div id="uploadCard" class="bg-white rounded-xl shadow-sm border border-gray-100 mb-6 overflow-hidden" style="display:none;">
      <div class="bg-gray-50 px-4 py-3 border-b border-gray-100">
        <h2 class="text-lg font-medium text-gray-800">Upload File</h2>
      </div>
      
      <div class="p-4 md:p-5">
        <!-- Selected path -->
        <div class="mb-4 px-3 py-2 bg-gray-50 rounded-md text-sm">
          <span class="text-gray-500">Selected folder:</span> 
          <span id="selectedFolderPath" class="text-gray-900 font-medium ml-1"></span>
        </div>
        
        <!-- Drop zone -->
        <div 
          id="dropZone" 
          onclick="document.getElementById('fileInput').click()" 
          class="border-2 border-dashed border-gray-300 rounded-xl p-8 text-center cursor-pointer transition-colors hover:border-primary hover:bg-blue-50/30 group">
          <div class="flex flex-col items-center">
            <i class="fas fa-cloud-upload-alt text-4xl text-gray-300 group-hover:text-primary transition-colors mb-3"></i>
            <p class="text-gray-500 group-hover:text-gray-700">Click or drag & drop a file here to upload</p>
            <p class="text-xs text-gray-400 mt-2">Maximum file size depends on your server configuration</p>
          </div>
        </div>
        
        <!-- Upload buttons -->
        <div class="flex mt-4 gap-3 justify-center">
          <div>
            <input type="file" id="fileInput" class="hidden" onchange="onFileSelected()" />
            <button 
              onclick="document.getElementById('fileInput').click()" 
              class="bg-white border border-gray-200 hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-lg flex items-center gap-2 transition-colors">
              <i class="fas fa-file"></i>
              <span>Select File</span>
            </button>
          </div>
          <div>
            <input type="file" id="folderInput" class="hidden" webkitdirectory directory multiple onchange="onFolderSelected()" />
            <button 
              onclick="document.getElementById('folderInput').click()" 
              class="bg-white border border-gray-200 hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-lg flex items-center gap-2 transition-colors">
              <i class="fas fa-folder"></i>
              <span>Select Folder</span>
            </button>
          </div>
        </div>
        
        <!-- File info -->
        <div id="fileInfo" class="mt-4"></div>
        
        <!-- Upload button -->
        <button 
          id="startUploadBtn" 
          onclick="startUpload()" 
          class="mt-4 bg-primary hover:bg-primary-dark text-white px-6 py-2 rounded-lg flex items-center gap-2 transition-colors hidden">
          <i class="fas fa-upload"></i>
          <span>Upload</span>
        </button>
        
        <!-- Progress bar -->
        <div id="uploadProgress" class="mt-5 hidden">
          <div class="flex justify-between text-xs text-gray-500 mb-1">
            <span>Progress</span>
            <span id="uploadPercent">0%</span>
          </div>
          <div class="h-2 w-full bg-gray-100 rounded-full overflow-hidden">
            <div id="uploadProgressBar" class="h-full w-0 bg-green-500 transition-all duration-300"></div>
          </div>
          <div id="uploadSpeed" class="text-sm text-gray-600 mt-2"></div>
          <div id="uploadStatus" class="text-sm text-gray-600 mt-2"></div>
          
          <!-- Cancel button -->
          <button 
            id="cancelUploadBtn" 
            onclick="cancelUpload()" 
            class="mt-4 bg-red-500 hover:bg-red-600 text-white px-6 py-2 rounded-lg flex items-center gap-2 transition-colors hidden">
            <i class="fas fa-times"></i>
            <span>Cancel Upload</span>
          </button>
        </div>
      </div>
    </div>

    <!-- Files card -->
    <div id="filesCard" class="bg-white rounded-xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
      <div class="bg-gray-50 px-4 py-3 border-b border-gray-100 flex justify-between items-center">
        <h2 class="text-lg font-medium text-gray-800">Files</h2>
        <div class="flex gap-2">
          <button id="downloadSelectedBtn" onclick="downloadSelected()" disabled 
            class="bg-primary text-white px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition-colors opacity-50 cursor-not-allowed text-sm">
            <i class="fas fa-download text-xs"></i>
            <span>Download Selected</span>
          </button>
          <button onclick="refreshFiles()" class="text-gray-500 hover:text-gray-700 bg-white border border-gray-200 px-3 py-1.5 rounded-lg text-sm transition-colors flex items-center gap-1.5">
            <i class="fas fa-sync-alt text-xs"></i>
            <span>Refresh</span>
          </button>
        </div>
      </div>
      
      <div class="p-4 md:p-5">
        <!-- Selected path and stats -->
        <div class="flex flex-wrap justify-between items-center mb-4 px-3 py-2 bg-gray-50 rounded-md text-sm">
          <div>
            <span class="text-gray-500">Current folder:</span> 
            <span id="fileListPath" class="text-gray-900 font-medium ml-1"></span>
          </div>
          <div id="fileStats" class="text-gray-500 mt-1 md:mt-0">
            <span id="fileCount">0 files</span>
            <span class="mx-1">•</span>
            <span id="totalSize">0 bytes</span>
          </div>
        </div>
        
        <!-- Files listing -->
        <div id="fileContainer" class="min-h-[200px] border border-gray-100 rounded-lg p-4 bg-gray-50/50">
          <div class="flex items-center justify-center h-[200px] text-gray-500">
            <div class="flex items-center gap-2">
              <i class="fas fa-spinner fa-spin text-primary"></i>
              <span>Loading files...</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </main>

  <!-- Toast notifications -->
  <div id="toast" class="fixed bottom-4 left-4 flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg text-white opacity-0 transform translate-y-8 transition-all duration-300 z-50 max-w-md">
    <i class="fas fa-check-circle"></i>
    <span id="toastMsg"></span>
  </div>

  <script>
    const API_BASE = window.location.origin;
    let currentPath = '/';
    let historyStack = [];
    let isUploading = false; // Track upload state
    let cancelUploadRequested = false; // Track cancel request
    let uploadStartTime = 0; // For speed calculation
    let currentXhr = null; // Store the current XHR request
    let selectedFiles = new Set(); // Track selected files for download

    // Variables for folder upload tracking
    let isUploadingFolder = false;
    let folderFiles = [];
    let currentFolderIndex = 0;
    let folderUploadProgress = 0;
    let folderName = '';

    window.addEventListener('load', () => {
      loadFolders('/');
      // Also load files in the current directory
      loadFiles('/');
    });

    function loadFolders(dir, pushHistory=true) {
      const container = document.getElementById('folderContainer');
      container.innerHTML = `
        <div class="flex items-center justify-center h-[120px] text-gray-500">
          <div class="flex items-center gap-2">
            <i class="fas fa-spinner fa-spin text-primary"></i>
            <span>Loading folders...</span>
          </div>
        </div>`;
      
      if (pushHistory && currentPath !== dir) {
        historyStack.push(currentPath);
      }
      
      fetch(`/list-folders?path=${encodeURIComponent(dir)}`)
        .then(r => r.json())
        .then(data => {
          if (data.error) {
            container.innerHTML = `<div class="text-red-500 p-4">Error: ${data.error}</div>`;
            return;
          }
          
          currentPath = data.path;
          
          // Update file list for the new path
          document.getElementById('fileListPath').textContent = currentPath;
          loadFiles(currentPath);
          
          if (data.isRoot) {
            // On Windows root, fetch drives
            return loadDrives();
          }
          
          updateBreadcrumb(currentPath);
          
          if (!data.folders || data.folders.length === 0) {
            container.innerHTML = `
              <div class="flex flex-col items-center justify-center h-[120px] text-gray-500">
                <i class="fas fa-folder-open text-gray-300 text-4xl mb-2"></i>
                <span>No subfolders here</span>
              </div>`;
          } else {
            container.innerHTML = '';
            const list = document.createElement('div');
            list.className = 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3';
            
            data.folders.forEach(folderName => {
              const div = document.createElement('div');
              div.className = 'bg-white border border-gray-200 rounded-lg p-3 cursor-pointer hover:border-primary hover:shadow-sm transition-all flex items-center gap-2 truncate';
              div.innerHTML = `
                <i class="fas fa-folder text-yellow-500"></i>
                <span class="truncate">${folderName}</span>
              `;
              div.onclick = () => {
                loadFolders(pathJoin(currentPath, folderName));
              };
              list.appendChild(div);
            });
            
            container.appendChild(list);
          }
        })
        .catch(err => {
          container.innerHTML = `<div class="text-red-500 p-4">Failed to load folders: ${err}</div>`;
        });
    }

    function loadDrives() {
      fetch('/list-drives')
        .then(r => r.json())
        .then(data => {
          updateBreadcrumb('My Computer');
          const container = document.getElementById('folderContainer');
          
          if (data.drives && data.drives.length) {
            container.innerHTML = '';
            const list = document.createElement('div');
            list.className = 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3';
            
            data.drives.forEach(drv => {
              const div = document.createElement('div');
              div.className = 'bg-white border border-gray-200 rounded-lg p-3 cursor-pointer hover:border-primary hover:shadow-sm transition-all flex items-center gap-2';
              div.innerHTML = `
                <i class="fas fa-hdd text-blue-500"></i>
                <span>${drv}</span>
              `;
              div.onclick = () => loadFolders(drv);
              list.appendChild(div);
            });
            
            container.appendChild(list);
          } else {
            container.innerHTML = `
              <div class="flex flex-col items-center justify-center h-[120px] text-gray-500">
                <i class="fas fa-hdd text-gray-300 text-4xl mb-2"></i>
                <span>No drives found</span>
              </div>`;
          }
        })
        .catch(err => {
          document.getElementById('folderContainer').innerHTML = `
            <div class="text-red-500 p-4">Error loading drives: ${err}</div>`;
        });
    }

    function goBack() {
      if (historyStack.length === 0) {
        showToast('No previous folder', 'info');
        return;
      }
      const prev = historyStack.pop();
      loadFolders(prev, false);
    }

    function goUp() {
      if (currentPath === '/' || currentPath === 'My Computer') {
        showToast('Already at root', 'info');
        return;
      }
      // If windows drive
      if (/^[A-Za-z]:\\?$/.test(currentPath)) {
        loadFolders('/'); // go back to "My Computer"
        return;
      }
      const parts = currentPath.split(/[\\/]/).filter(Boolean);
      parts.pop();
      let upPath = parts.join('/');
      if (upPath === '') upPath = '/';
      loadFolders(upPath);
    }

    function updateBreadcrumb(label) {
      const bc = document.getElementById('breadcrumb');
      bc.innerHTML = `<span class="font-medium text-primary-dark hover:underline cursor-pointer">${label}</span>`;
    }

    function pathJoin(base, folder) {
      if (base === '/') return '/' + folder;
      // For Windows:
      if (/^[A-Za-z]:\\?$/.test(base)) {
        return base.replace(/\\?$/, '') + '\\' + folder;
      }
      // Normal path
      return base.replace(/\/$/, '') + '/' + folder;
    }

    function selectCurrentFolder() {
      document.getElementById('uploadCard').style.display = 'block';
      document.getElementById('selectedFolderPath').textContent = currentPath;
      document.getElementById('fileListPath').textContent = currentPath;
      showToast('Folder selected: ' + currentPath, 'info');
      // Reset upload UI
      document.getElementById('fileInfo').innerHTML = '';
      document.getElementById('startUploadBtn').style.display = 'none';
      document.getElementById('uploadProgress').style.display = 'none';
      document.getElementById('uploadStatus').innerHTML = '';
      
      // Scroll to upload card
      document.getElementById('uploadCard').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }

    // Set UI state during upload
    function setUploadingState(uploading) {
      isUploading = uploading;
      
      // Disable or enable explorer card
      const explorerCard = document.getElementById('explorerCard');
      if (uploading) {
        explorerCard.classList.add('opacity-50', 'pointer-events-none');
      } else {
        explorerCard.classList.remove('opacity-50', 'pointer-events-none');
      }
      
      // Show/hide cancel button and disable upload button
      const startUploadBtn = document.getElementById('startUploadBtn');
      const cancelUploadBtn = document.getElementById('cancelUploadBtn');
      
      if (uploading) {
        startUploadBtn.disabled = true;
        startUploadBtn.classList.add('opacity-50', 'cursor-not-allowed');
        cancelUploadBtn.classList.remove('hidden');
      } else {
        startUploadBtn.disabled = false;
        startUploadBtn.classList.remove('opacity-50', 'cursor-not-allowed');
        cancelUploadBtn.classList.add('hidden');
        
        // Reset cancel flag
        cancelUploadRequested = false;
      }
      
      // Disable drop zone
      const dropZone = document.getElementById('dropZone');
      if (uploading) {
        dropZone.classList.add('opacity-50', 'pointer-events-none');
      } else {
        dropZone.classList.remove('opacity-50', 'pointer-events-none');
      }
    }
    
    // Cancel ongoing upload
    function cancelUpload() {
      if (!isUploading) return;
      
      cancelUploadRequested = true;
      
      if (currentXhr) {
        currentXhr.abort();
      }
      
      document.getElementById('uploadStatus').textContent = 'Cancelling upload...';
      
      // Send request to server to delete temporary files
      const fileName = selectedFile ? selectedFile.name : '';
      fetch(`/cancel-upload?fileId=${fileId}&fileName=${encodeURIComponent(fileName)}`, {
        method: 'POST'
      })
      .then(response => response.json())
      .then(data => {
        setUploadingState(false);
        document.getElementById('uploadStatus').textContent = 'Upload cancelled';
        document.getElementById('uploadStatus').className = 'text-sm text-yellow-500 mt-2';
        showToast('Upload cancelled', 'info');
      })
      .catch(error => {
        console.error('Error cancelling upload:', error);
        setUploadingState(false);
      });
    }
    
    // Calculate and format upload speed
    function calculateSpeed(bytesUploaded, elapsedMs) {
      if (elapsedMs === 0) return 'N/A';
      
      const bytesPerSecond = (bytesUploaded / elapsedMs) * 1000;
      
      if (bytesPerSecond < 1024) {
        return bytesPerSecond.toFixed(1) + ' B/s';
      } else if (bytesPerSecond < 1024 * 1024) {
        return (bytesPerSecond / 1024).toFixed(1) + ' KB/s';
      } else if (bytesPerSecond < 1024 * 1024 * 1024) {
        return (bytesPerSecond / (1024 * 1024)).toFixed(1) + ' MB/s';
      } else {
        return (bytesPerSecond / (1024 * 1024 * 1024)).toFixed(1) + ' GB/s';
      }
    }

    // File selection + upload
    let selectedFile = null;
    function onFileSelected() {
      const input = document.getElementById('fileInput');
      selectedFile = input.files[0];
      if (!selectedFile) return;
      
      document.getElementById('fileInfo').innerHTML = `
        <div class="bg-blue-50 border border-blue-100 rounded-lg p-3 flex items-start">
          <div class="bg-blue-100 rounded-lg p-2 text-blue-700">
            <i class="fas fa-file-alt text-xl"></i>
          </div>
          <div class="ml-3">
            <div class="font-medium">${selectedFile.name}</div>
            <div class="text-sm text-gray-500 mt-0.5">${formatSize(selectedFile.size)}</div>
          </div>
        </div>`;
      
      document.getElementById('startUploadBtn').style.display = 'flex';
    }

    function onFolderSelected() {
      const input = document.getElementById('folderInput');
      const files = input.files;
      
      if (!files || files.length === 0) return;
      
      // Get the folder name from the path of the first file
      const firstPath = files[0].webkitRelativePath;
      folderName = firstPath.split('/')[0];
      
      // Store all files
      folderFiles = Array.from(files);
      
      // Update UI
      document.getElementById('fileInfo').innerHTML = `
        <div class="bg-blue-50 border border-blue-100 rounded-lg p-3 flex items-start">
          <div class="bg-blue-100 rounded-lg p-2 text-blue-700">
            <i class="fas fa-folder text-xl"></i>
          </div>
          <div class="ml-3">
            <div class="font-medium">${folderName}</div>
            <div class="text-sm text-gray-500 mt-0.5">${folderFiles.length} files (${formatSize(getTotalSize(folderFiles))})</div>
          </div>
        </div>`;
      
      document.getElementById('startUploadBtn').style.display = 'flex';
      
      // Set selectedFile to null to indicate we're using folder upload
      selectedFile = null;
    }

    function getTotalSize(files) {
      return files.reduce((total, file) => total + file.size, 0);
    }

    function startUpload() {
      if (isUploading) {
        showToast('Upload already in progress', 'info');
        return;
      }
      
      const folder = currentPath;
      if (!folder) {
        showToast('No target folder', 'error');
        return;
      }

      // Set upload start time for speed calculation
      uploadStartTime = Date.now();
      
      // Set uploading state
      setUploadingState(true);
      
      // Check if we're uploading a folder or a file
      if (selectedFile) {
        // Existing file upload logic
        const MAX_SINGLE_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
        if (selectedFile.size <= MAX_SINGLE_CHUNK_SIZE) {
          // single chunk
          uploadSingleChunk(selectedFile, folder);
        } else {
          // multi-chunk
          uploadMultiChunk(selectedFile, folder);
        }
      } else if (folderFiles.length > 0) {
        // Folder upload logic
        isUploadingFolder = true;
        currentFolderIndex = 0;
        folderUploadProgress = 0;
        
        showToast(`Uploading folder ${folderName}`, 'info');
        document.getElementById('uploadProgress').style.display = 'block';
        document.getElementById('uploadProgressBar').style.width = '0%';
        document.getElementById('uploadPercent').textContent = '0%';
        document.getElementById('uploadSpeed').textContent = 'Calculating...';
        document.getElementById('uploadStatus').textContent = `Preparing to upload ${folderFiles.length} files...`;
        
        // Create folder structure on server then start uploads
        uploadNextFolderFile(folder);
      } else {
        showToast('No file or folder selected', 'error');
        setUploadingState(false);
      }
    }

    function uploadNextFolderFile(targetFolder) {
      if (cancelUploadRequested) {
        isUploadingFolder = false;
        return;
      }
      
      if (currentFolderIndex >= folderFiles.length) {
        // All files in folder uploaded
        document.getElementById('uploadProgressBar').style.width = '100%';
        document.getElementById('uploadPercent').textContent = '100%';
        document.getElementById('uploadStatus').textContent = 'Folder upload complete!';
        document.getElementById('uploadStatus').className = 'text-sm text-green-600 mt-2 font-medium';
        
        // Final speed calculation
        const elapsedMs = Date.now() - uploadStartTime;
        const totalSize = getTotalSize(folderFiles);
        const speed = calculateSpeed(totalSize, elapsedMs);
        document.getElementById('uploadSpeed').textContent = `Average: ${speed}`;
        
        showToast('Folder upload complete!', 'success');
        isUploadingFolder = false;
        setUploadingState(false);
        return;
      }
      
      // Get next file
      const file = folderFiles[currentFolderIndex];
      
      // Get relative path within the folder
      const relativePath = file.webkitRelativePath;
      const pathParts = relativePath.split('/');
      // Remove the first component (folder name) as we'll recreate it
      pathParts.shift();
      
      // Determine if there are subfolders in the path
      const hasSubfolders = pathParts.length > 1;
      const folderPath = hasSubfolders ? pathParts.slice(0, -1).join('/') : '';
      const fileName = pathParts[pathParts.length - 1];
      
      // Create the full path
      let uploadPath = targetFolder;
      if (uploadPath === '/') uploadPath = '';
      
      // Add the folder name as the root folder
      const fullPath = `${uploadPath}/${folderName}${folderPath ? '/' + folderPath : ''}`;
      
      document.getElementById('uploadStatus').textContent = `Uploading file ${currentFolderIndex + 1}/${folderFiles.length}: ${relativePath}`;

      // Update progress for the entire folder
      const totalSize = getTotalSize(folderFiles);
      const fileWeight = file.size / totalSize;
      
      // Upload the file
      if (file.size <= 10 * 1024 * 1024) { // 10MB limit for single chunk
        uploadFolderFileSingleChunk(file, fullPath, fileName, fileWeight);
      } else {
        // For larger files, use multi-chunk upload
        // This would need additional implementation to track folder progress too
        uploadFolderFileMultiChunk(file, fullPath, fileName, fileWeight);
      }
    }

    function uploadFolderFileSingleChunk(file, folderPath, fileName, fileWeight) {
      const xhr = new XMLHttpRequest();
      currentXhr = xhr; // Store reference for cancellation
      
      xhr.open('POST', `/upload-chunk?path=${encodeURIComponent(folderPath)}`, true);
      xhr.setRequestHeader('X-File-Name', encodeURIComponent(fileName));
      xhr.setRequestHeader('X-Chunk-Index', '0');
      xhr.setRequestHeader('X-Total-Chunks', '1');
      xhr.setRequestHeader('X-Chunk-Size', file.size);

      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          const pct = Math.floor((e.loaded / e.total) * 100);
          
          // Calculate the contribution of this file to the overall folder progress
          const fileProgress = (pct / 100) * fileWeight;
          const baseProgress = folderUploadProgress;
          const overallProgress = Math.floor(baseProgress + fileProgress * 100);
          
          document.getElementById('uploadProgressBar').style.width = overallProgress + '%';
          document.getElementById('uploadPercent').textContent = overallProgress + '%';
          
          // Speed calculation for the entire folder upload
          const elapsedMs = Date.now() - uploadStartTime;
          const totalSizeUploaded = getTotalUploadedSize(currentFolderIndex, e.loaded);
          const speed = calculateSpeed(totalSizeUploaded, elapsedMs);
          document.getElementById('uploadSpeed').textContent = speed;
        }
      };
      
      xhr.onload = () => {
        if (cancelUploadRequested) {
          return;
        }
        
        if (xhr.status === 200) {
          try {
            const resp = JSON.parse(xhr.responseText);
            if (resp.success) {
              // Update folder progress
              folderUploadProgress += fileWeight * 100;
              currentFolderIndex++;
              // Continue with next file
              uploadNextFolderFile(folderPath);
            } else {
              showToast(resp.error || 'Server error', 'error');
              document.getElementById('uploadStatus').textContent = `Error uploading ${fileName}: ${resp.error || 'Server error'}`;
              document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
              setUploadingState(false);
              isUploadingFolder = false;
            }
          } catch {
            showToast('Invalid response from server', 'error');
            document.getElementById('uploadStatus').textContent = 'Invalid response from server';
            document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
            setUploadingState(false);
            isUploadingFolder = false;
          }
        } else {
          showToast(`Error: ${xhr.status}`, 'error');
          document.getElementById('uploadStatus').textContent = `Error: ${xhr.status}`;
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
          isUploadingFolder = false;
        }
      };
      
      xhr.onerror = () => {
        if (!cancelUploadRequested) {
          showToast('Network error', 'error');
          document.getElementById('uploadStatus').textContent = 'Network error';
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
          isUploadingFolder = false;
        }
      };
      
      xhr.onabort = () => {
        document.getElementById('uploadStatus').textContent = 'Upload aborted';
        document.getElementById('uploadStatus').className = 'text-sm text-yellow-500 mt-2';
        isUploadingFolder = false;
      };

      xhr.send(file);
    }

    function uploadFolderFileMultiChunk(file, folderPath, fileName, fileWeight) {
      // Similar to uploadMultiChunk but adapted for folder upload
      // This would need custom implementation
      // For simplicity, we'll just use the single chunk method with a warning
      document.getElementById('uploadStatus').textContent = `Warning: Large file (${formatSize(file.size)}) being uploaded as single chunk`;
      uploadFolderFileSingleChunk(file, folderPath, fileName, fileWeight);
    }

    function getTotalUploadedSize(currentIndex, currentProgress) {
      // Calculate total bytes uploaded so far
      let totalUploaded = 0;
      
      // Add size of all completed files
      for (let i = 0; i < currentIndex; i++) {
        totalUploaded += folderFiles[i].size;
      }
      
      // Add partial progress of current file
      if (currentIndex < folderFiles.length) {
        totalUploaded += currentProgress;
      }
      
      return totalUploaded;
    }

    // Single file upload functions
    function uploadSingleChunk(file, folder) {
      showToast(`Uploading ${file.name}`, 'info');
      document.getElementById('uploadProgress').style.display = 'block';
      document.getElementById('uploadProgressBar').style.width = '0%';
      document.getElementById('uploadPercent').textContent = '0%';
      document.getElementById('uploadSpeed').textContent = 'Calculating...';
      document.getElementById('uploadStatus').textContent = 'Uploading...';

      const xhr = new XMLHttpRequest();
      currentXhr = xhr; // Store reference for cancellation
      
      xhr.open('POST', `/upload-chunk?path=${encodeURIComponent(folder)}`, true);
      xhr.setRequestHeader('X-File-Name', encodeURIComponent(file.name));
      xhr.setRequestHeader('X-Chunk-Index', '0');
      xhr.setRequestHeader('X-Total-Chunks', '1');
      xhr.setRequestHeader('X-Chunk-Size', file.size);

      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          const pct = Math.floor((e.loaded / e.total) * 100);
          document.getElementById('uploadProgressBar').style.width = pct + '%';
          document.getElementById('uploadPercent').textContent = pct + '%';
          
          // Calculate and display speed
          const elapsedMs = Date.now() - uploadStartTime;
          const speed = calculateSpeed(e.loaded, elapsedMs);
          document.getElementById('uploadSpeed').textContent = speed;
          
          const remainingBytes = e.total - e.loaded;
          if (e.loaded > 0 && elapsedMs > 1000) {
            const bytesPerMs = e.loaded / elapsedMs;
            const remainingTimeMs = bytesPerMs > 0 ? remainingBytes / bytesPerMs : 0;
            const remainingTimeSec = Math.ceil(remainingTimeMs / 1000);
            
            if (remainingTimeSec > 0) {
              let timeDisplay = '';
              if (remainingTimeSec > 60) {
                const mins = Math.floor(remainingTimeSec / 60);
                const secs = remainingTimeSec % 60;
                timeDisplay = `${mins}m ${secs}s remaining`;
              } else {
                timeDisplay = `${remainingTimeSec}s remaining`;
              }
              
              document.getElementById('uploadStatus').textContent = `Uploading... ${timeDisplay}`;
            }
          }
        }
      };
      
      xhr.onload = () => {
        if (cancelUploadRequested) {
          return; // Don't continue if cancellation was requested
        }
        
        if (xhr.status === 200) {
          try {
            const resp = JSON.parse(xhr.responseText);
            if (resp.success) {
              document.getElementById('uploadProgressBar').style.width = '100%';
              document.getElementById('uploadPercent').textContent = '100%';
              document.getElementById('uploadStatus').textContent = 'Upload complete!';
              document.getElementById('uploadStatus').className = 'text-sm text-green-600 mt-2 font-medium';
              
              // Final speed calculation
              const elapsedMs = Date.now() - uploadStartTime;
              const speed = calculateSpeed(file.size, elapsedMs);
              document.getElementById('uploadSpeed').textContent = `Average: ${speed}`;
              
              showToast('Upload complete!', 'success');
              setUploadingState(false);
            } else {
              showToast(resp.error || 'Server error', 'error');
              document.getElementById('uploadStatus').textContent = resp.error || 'Server error';
              document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
              setUploadingState(false);
            }
          } catch {
            showToast('Invalid response from server', 'error');
            document.getElementById('uploadStatus').textContent = 'Invalid response from server';
            document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
            setUploadingState(false);
          }
        } else {
          showToast(`Error: ${xhr.status}`, 'error');
          document.getElementById('uploadStatus').textContent = `Error: ${xhr.status}`;
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
        }
      };
      
      xhr.onerror = () => {
        if (!cancelUploadRequested) {
          showToast('Network error', 'error');
          document.getElementById('uploadStatus').textContent = 'Network error';
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
        }
      };
      
      xhr.onabort = () => {
        document.getElementById('uploadStatus').textContent = 'Upload aborted';
        document.getElementById('uploadStatus').className = 'text-sm text-yellow-500 mt-2';
      };

      xhr.send(file);
    }

    // MULTI-CHUNK
    let chunkSize = 5 * 1024 * 1024;
    let currentChunkIndex = 0;
    let totalChunks = 0;
    let fileId = null;
    let totalBytesUploaded = 0;

    function uploadMultiChunk(file, folder) {
      // Setup UI
      showToast(`Uploading ${file.name} in multiple chunks`, 'info');
      document.getElementById('uploadProgress').style.display = 'block';
      document.getElementById('uploadProgressBar').style.width = '0%';
      document.getElementById('uploadPercent').textContent = '0%';
      document.getElementById('uploadSpeed').textContent = 'Calculating...';
      document.getElementById('uploadStatus').textContent = 'Preparing...';

      // Setup state
      chunkSize = 5 * 1024 * 1024; // 5MB chunks
      totalChunks = Math.ceil(file.size / chunkSize);
      currentChunkIndex = 0;
      fileId = Date.now().toString(); // or any unique ID
      totalBytesUploaded = 0;

      // Start
      uploadNextChunk(file, folder);
    }

    function uploadNextChunk(file, folder) {
      if (cancelUploadRequested) {
        return; // Don't continue if cancellation was requested
      }
      
      if (currentChunkIndex >= totalChunks) {
        // done
        showToast('All chunks sent!', 'info');
        setUploadingState(false);
        return;
      }
      
      const start = currentChunkIndex * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      const blob = file.slice(start, end);

      const xhr = new XMLHttpRequest();
      currentXhr = xhr; // Store reference for cancellation
      
      xhr.open('POST', `/upload-chunk?path=${encodeURIComponent(folder)}`, true);
      xhr.setRequestHeader('X-File-Name', encodeURIComponent(file.name));
      xhr.setRequestHeader('X-Chunk-Index', currentChunkIndex);
      xhr.setRequestHeader('X-Total-Chunks', totalChunks);
      xhr.setRequestHeader('X-File-Id', fileId);
      xhr.setRequestHeader('X-Chunk-Size', blob.size);

      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          const chunkPercent = e.loaded / e.total;
          const currentChunkUploaded = e.loaded;
          const overallBytes = totalBytesUploaded + currentChunkUploaded;
          const overallPercent = Math.floor((overallBytes / file.size) * 100);
          
          document.getElementById('uploadProgressBar').style.width = overallPercent + '%';
          document.getElementById('uploadPercent').textContent = overallPercent + '%';
          
          // Calculate and display speed
          const elapsedMs = Date.now() - uploadStartTime;
          const speed = calculateSpeed(overallBytes, elapsedMs);
          document.getElementById('uploadSpeed').textContent = speed;
          
          // Estimate remaining time
          if (overallBytes > 0 && elapsedMs > 1000) {
            const bytesPerMs = overallBytes / elapsedMs;
            const remainingBytes = file.size - overallBytes;
            const remainingTimeMs = bytesPerMs > 0 ? remainingBytes / bytesPerMs : 0;
            const remainingTimeSec = Math.ceil(remainingTimeMs / 1000);
            
            let timeDisplay = '';
            if (remainingTimeSec > 60) {
              const mins = Math.floor(remainingTimeSec / 60);
              const secs = remainingTimeSec % 60;
              timeDisplay = `${mins}m ${secs}s remaining`;
            } else {
              timeDisplay = `${remainingTimeSec}s remaining`;
            }
            
            document.getElementById('uploadStatus').textContent = 
              `Uploading chunk ${currentChunkIndex+1} of ${totalChunks} - ${overallPercent}% overall - ${timeDisplay}`;
          } else {
            document.getElementById('uploadStatus').textContent = 
              `Uploading chunk ${currentChunkIndex+1} of ${totalChunks} - ${overallPercent}% overall`;
          }
        }
      };

      xhr.onload = () => {
        if (cancelUploadRequested) {
          return; // Don't continue if cancellation was requested
        }
        
        if (xhr.status === 200) {
          try {
            const resp = JSON.parse(xhr.responseText);
            if (!resp.success) {
              showToast(resp.error || 'Server error uploading chunk', 'error');
              document.getElementById('uploadStatus').textContent = resp.error || 'Server error uploading chunk';
              document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
              setUploadingState(false);
              return;
            }
            
            // Add current chunk size to total bytes uploaded
            totalBytesUploaded += blob.size;
            
            // success
            if (currentChunkIndex === totalChunks - 1) {
              // last chunk => server finalizes
              document.getElementById('uploadProgressBar').style.width = '100%';
              document.getElementById('uploadPercent').textContent = '100%';
              document.getElementById('uploadStatus').textContent = 'Upload complete!';
              document.getElementById('uploadStatus').className = 'text-sm text-green-600 mt-2 font-medium';
              
              // Final speed calculation
              const elapsedMs = Date.now() - uploadStartTime;
              const speed = calculateSpeed(file.size, elapsedMs);
              document.getElementById('uploadSpeed').textContent = `Average: ${speed}`;
              
              showToast('Upload complete!', 'success');
              setUploadingState(false);
            } else {
              currentChunkIndex++;
              uploadNextChunk(file, folder);
            }
          } catch {
            showToast('Invalid JSON response from server', 'error');
            document.getElementById('uploadStatus').textContent = 'Invalid JSON response from server';
            document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
            setUploadingState(false);
          }
        } else {
          showToast(`Error uploading chunk ${currentChunkIndex}`, 'error');
          document.getElementById('uploadStatus').textContent = `Error uploading chunk ${currentChunkIndex}`;
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
        }
      };
      
      xhr.onerror = () => {
        if (!cancelUploadRequested) {
          showToast(`Network error uploading chunk ${currentChunkIndex}`, 'error');
          document.getElementById('uploadStatus').textContent = `Network error uploading chunk ${currentChunkIndex}`;
          document.getElementById('uploadStatus').className = 'text-sm text-red-500 mt-2';
          setUploadingState(false);
        }
      };
      
      xhr.onabort = () => {
        document.getElementById('uploadStatus').textContent = 'Upload aborted';
        document.getElementById('uploadStatus').className = 'text-sm text-yellow-500 mt-2';
      };
      
      xhr.send(blob);
    }

    // Drag drop
    const dropZone = document.getElementById('dropZone');
    
    dropZone.addEventListener('dragover', e => {
      e.preventDefault();
      e.stopPropagation();
      dropZone.classList.add('border-primary', 'bg-blue-50/30');
    });
    
    dropZone.addEventListener('dragleave', e => {
      e.preventDefault();
      e.stopPropagation();
      dropZone.classList.remove('border-primary', 'bg-blue-50/30');
    });
    
    dropZone.addEventListener('drop', e => {
      e.preventDefault();
      e.stopPropagation();
      dropZone.classList.remove('border-primary', 'bg-blue-50/30');
      
      if (e.dataTransfer.files.length) {
        document.getElementById('fileInput').files = e.dataTransfer.files;
        onFileSelected();
      }
    });

    // Toast
    function showToast(msg, type='info', duration=4000) {
      const toast = document.getElementById('toast');
      const icon = toast.querySelector('i');
      const text = document.getElementById('toastMsg');
      
      text.textContent = msg;
      toast.className = 'fixed bottom-4 left-4 flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg text-white opacity-0 transform translate-y-8 transition-all duration-300 z-50 max-w-md'; // reset
      
      if (type === 'success') {
        toast.classList.add('bg-green-500');
        icon.className = 'fas fa-check-circle';
      } else if (type === 'error') {
        toast.classList.add('bg-red-500');
        icon.className = 'fas fa-times-circle';
      } else {
        toast.classList.add('bg-primary');
        icon.className = 'fas fa-info-circle';
      }

      toast.classList.add('opacity-100', 'translate-y-0');
      
      setTimeout(() => {
        toast.classList.remove('opacity-100', 'translate-y-0');
        toast.classList.add('opacity-0', 'translate-y-8');
      }, duration);
    }

    function formatSize(bytes) {
      if (bytes === 0) return '0 B';
      const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
      const i = Math.floor(Math.log(bytes) / Math.log(1024));
      return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
    }

    function showHelp() {
      // Create a modern modal instead of using alert
      const modal = document.createElement('div');
      modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4';
      modal.innerHTML = `
        <div class="bg-white rounded-xl shadow-lg max-w-md w-full p-5 transform transition-all">
          <div class="flex justify-between items-center mb-4">
            <h3 class="text-lg font-semibold text-gray-800">How to Use</h3>
            <button class="text-gray-400 hover:text-gray-500" id="closeHelpModal">
              <i class="fas fa-times"></i>
            </button>
          </div>
          <div class="space-y-3 text-gray-600">
            <div class="flex items-start gap-3">
              <div class="bg-primary/10 text-primary rounded-full w-6 h-6 flex items-center justify-center flex-shrink-0 mt-0.5">1</div>
              <p>Navigate to desired folder using the file explorer</p>
            </div>
            <div class="flex items-start gap-3">
              <div class="bg-primary/10 text-primary rounded-full w-6 h-6 flex items-center justify-center flex-shrink-0 mt-0.5">2</div>
              <p>Click "Select This Folder" to choose the upload destination</p>
            </div>
            <div class="flex items-start gap-3">
              <div class="bg-primary/10 text-primary rounded-full w-6 h-6 flex items-center justify-center flex-shrink-0 mt-0.5">3</div>
              <p>Click or drag & drop a file to the upload area</p>
            </div>
            <div class="flex items-start gap-3">
              <div class="bg-primary/10 text-primary rounded-full w-6 h-6 flex items-center justify-center flex-shrink-0 mt-0.5">4</div>
              <p>Click the Upload button to start uploading</p>
            </div>
          </div>
          <div class="mt-5 pt-3 border-t border-gray-100 text-xs text-gray-500">
            Large files are automatically split into chunks to prevent corruption during upload.
          </div>
        </div>
      `;
      
      document.body.appendChild(modal);
      
      document.getElementById('closeHelpModal').addEventListener('click', () => {
        modal.classList.add('opacity-0');
        setTimeout(() => {
          document.body.removeChild(modal);
        }, 300);
      });
    }

    // Load files in the selected folder
    function loadFiles(folderPath) {
      const container = document.getElementById('fileContainer');
      container.innerHTML = `
        <div class="flex items-center justify-center h-[200px] text-gray-500">
          <div class="flex items-center gap-2">
            <i class="fas fa-spinner fa-spin text-primary"></i>
            <span>Loading files...</span>
          </div>
        </div>`;

      fetch(`/list-files?path=${encodeURIComponent(folderPath)}`)
        .then(r => r.json())
        .then(data => {
          if (!data.success) {
            container.innerHTML = `<div class="text-red-500 p-4">Error: ${data.error || 'Failed to load files'}</div>`;
            return;
          }

          if (!data.files || data.files.length === 0) {
            container.innerHTML = `
              <div class="flex flex-col items-center justify-center h-[200px] text-gray-500">
                <i class="fas fa-file-alt text-gray-300 text-4xl mb-2"></i>
                <span>No files in this folder</span>
              </div>`;
            
            // Update stats
            document.getElementById('fileCount').textContent = '0 files';
            document.getElementById('totalSize').textContent = '0 bytes';
            return;
          }

          // Calculate total size and update stats
          let totalSize = 0;
          data.files.forEach(file => {
            totalSize += file.size || 0;
          });
          
          document.getElementById('fileCount').textContent = `${data.files.length} file${data.files.length !== 1 ? 's' : ''}`;
          document.getElementById('totalSize').textContent = formatSize(totalSize);

          // Clear selected files
          selectedFiles.clear();
          updateDownloadButton();

          // Create grid view for files
          container.innerHTML = `
            <div class="flex justify-between items-center border-b border-gray-100 pb-3 mb-3">
              <div class="flex items-center gap-2">
                <input type="checkbox" id="selectAllFiles" class="rounded border-gray-300" onchange="toggleSelectAll(this.checked)">
                <label for="selectAllFiles" class="text-sm text-gray-700 cursor-pointer">Select All</label>
              </div>
              <div class="text-xs text-gray-500">
                <button onclick="toggleViewMode()" class="p-1 rounded-md hover:bg-gray-100 flex items-center gap-1">
                  <i id="viewModeIcon" class="fas fa-th-large"></i>
                  <span id="viewModeText">Grid View</span>
                </button>
              </div>
            </div>
            <div id="fileGrid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3"></div>
            <div id="fileList" class="hidden"></div>
          `;
          
          const fileGrid = document.getElementById('fileGrid');
          const fileList = document.getElementById('fileList');
          
          // Populate grid view
          data.files.forEach((file, idx) => {
            const fileCard = document.createElement('div');
            fileCard.className = 'bg-white border border-gray-200 rounded-lg p-3 hover:shadow-sm transition-all';
            
            const icon = getFileIcon(file.type, file.extension);
            
            fileCard.innerHTML = `
              <div class="flex items-start">
                <div class="flex-shrink-0 p-2 rounded-lg ${getColorForFileType(file.type)} mb-2 mr-2">
                  <i class="${icon} text-white"></i>
                </div>
                <div class="flex-grow min-w-0">
                  <div class="flex items-start justify-between">
                    <div class="truncate max-w-[140px]" title="${file.name}">
                      <span class="font-medium text-gray-700">${file.name}</span>
                    </div>
                    <div class="ml-2 flex-shrink-0">
                      <input type="checkbox" id="file_grid_${idx}" data-file="${encodeURIComponent(folderPath + '/' + file.name)}" 
                        class="file-checkbox rounded border-gray-300" onchange="toggleFileSelection(this)">
                    </div>
                  </div>
                  <div class="text-xs text-gray-500 mt-1">${formatSize(file.size)}</div>
                  <div class="flex items-center justify-between mt-2">
                    <span class="text-xs text-gray-400">${formatDate(file.modified)}</span>
                    <button onclick="downloadSingleFile('${encodeURIComponent(folderPath + '/' + file.name)}')" 
                      class="bg-primary hover:bg-primary-dark text-white p-1 rounded-md transition-colors">
                      <i class="fas fa-download text-xs"></i>
                    </button>
                  </div>
                </div>
              </div>
            `;
            
            fileGrid.appendChild(fileCard);
          });
          
          // Populate list view (hidden by default)
          fileList.innerHTML = `
            <table class="w-full">
              <thead>
                <tr class="bg-gray-50 text-left text-gray-500 text-xs">
                  <th class="px-3 py-2 w-8"></th>
                  <th class="px-3 py-2">Name</th>
                  <th class="px-3 py-2 w-28">Size</th>
                  <th class="px-3 py-2 w-40">Modified</th>
                  <th class="px-3 py-2 w-20"></th>
                </tr>
              </thead>
              <tbody>
                ${data.files.map((file, idx) => `
                  <tr class="${idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'} border-t border-gray-100 hover:bg-blue-50/30">
                    <td class="px-3 py-2 w-8">
                      <input type="checkbox" id="file_list_${idx}" data-file="${encodeURIComponent(folderPath + '/' + file.name)}" 
                        class="file-checkbox rounded border-gray-300" onchange="toggleFileSelection(this)">
                    </td>
                    <td class="px-3 py-2 truncate max-w-xs">
                      <div class="flex items-center gap-2">
                        <i class="${getFileIcon(file.type, file.extension)} ${getTextColorForFileType(file.type)}"></i>
                        <span class="truncate">${file.name}</span>
                      </div>
                    </td>
                    <td class="px-3 py-2 text-gray-500 text-sm">${formatSize(file.size)}</td>
                    <td class="px-3 py-2 text-gray-500 text-sm">${formatDate(file.modified)}</td>
                    <td class="px-3 py-2">
                      <button onclick="downloadSingleFile('${encodeURIComponent(folderPath + '/' + file.name)}')" 
                        class="bg-primary hover:bg-primary-dark text-white p-1.5 rounded transition-colors">
                        <i class="fas fa-download"></i>
                      </button>
                    </td>
                  </tr>
                `).join('')}
              </tbody>
            </table>
          `;
          
          // Sync checkboxes between grid and list views
          syncCheckboxes();
        })
        .catch(err => {
          container.innerHTML = `<div class="text-red-500 p-4">Failed to load files: ${err}</div>`;
        });
    }

    // Toggle between grid and list view
    let currentView = 'grid'; // Default view
    function toggleViewMode() {
      const gridView = document.getElementById('fileGrid');
      const listView = document.getElementById('fileList');
      const icon = document.getElementById('viewModeIcon');
      const text = document.getElementById('viewModeText');
      
      if (currentView === 'grid') {
        gridView.classList.add('hidden');
        listView.classList.remove('hidden');
        icon.className = 'fas fa-list';
        text.textContent = 'List View';
        currentView = 'list';
      } else {
        gridView.classList.remove('hidden');
        listView.classList.add('hidden');
        icon.className = 'fas fa-th-large';
        text.textContent = 'Grid View';
        currentView = 'grid';
      }
      
      // Make sure all checkboxes are in sync
      syncCheckboxes();
    }

    // Function to sync checkbox states between grid and list views
    function syncCheckboxes() {
      // Get all file paths that should be checked
      const checkedPaths = Array.from(selectedFiles);
      
      // Uncheck all boxes first
      document.querySelectorAll('.file-checkbox').forEach(cb => {
        cb.checked = checkedPaths.includes(cb.dataset.file);
      });
      
      // Check the "select all" box if all files are selected
      const selectAllCheckbox = document.getElementById('selectAllFiles');
      const totalCheckboxes = document.querySelectorAll('.file-checkbox').length;
      selectAllCheckbox.checked = (totalCheckboxes > 0 && selectedFiles.size === totalCheckboxes);
    }

    // Get appropriate icon for file type
    function getFileIcon(type, extension) {
      switch (type) {
        case 'image':
          return 'fas fa-file-image';
        case 'document':
          if (extension === 'pdf') return 'fas fa-file-pdf';
          if (['doc', 'docx'].includes(extension)) return 'fas fa-file-word';
          if (['xls', 'xlsx', 'csv'].includes(extension)) return 'fas fa-file-excel';
          if (['ppt', 'pptx'].includes(extension)) return 'fas fa-file-powerpoint';
          return 'fas fa-file-alt';
        case 'archive':
          return 'fas fa-file-archive';
        case 'audio':
          return 'fas fa-file-audio';
        case 'video':
          return 'fas fa-file-video';
        case 'code':
          return 'fas fa-file-code';
        default:
          return 'fas fa-file';
      }
    }
    
    // Get background color for file type icon
    function getColorForFileType(type) {
      switch (type) {
        case 'image':
          return 'bg-purple-500';
        case 'document':
          return 'bg-blue-500';
        case 'archive':
          return 'bg-yellow-500';
        case 'audio':
          return 'bg-green-500';
        case 'video':
          return 'bg-red-500';
        case 'code':
          return 'bg-gray-700';
        default:
          return 'bg-gray-500';
      }
    }
    
    // Get text color for file type icon in list view
    function getTextColorForFileType(type) {
      switch (type) {
        case 'image':
          return 'text-purple-500';
        case 'document':
          return 'text-blue-500';
        case 'archive':
          return 'text-yellow-500';
        case 'audio':
          return 'text-green-500';
        case 'video':
          return 'text-red-500';
        case 'code':
          return 'text-gray-700';
        default:
          return 'text-gray-500';
      }
    }
    
    // Format date for display
    function formatDate(dateString) {
      if (!dateString) return '';
      
      const date = new Date(dateString);
      const now = new Date();
      const diff = now - date;
      
      // If today, show time
      if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
        return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      }
      
      // If this year, show month and day
      if (date.getFullYear() === now.getFullYear()) {
        return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
      }
      
      // Otherwise show full date
      return date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' });
    }

    // Toggle selection of all files
    function toggleSelectAll(checked) {
      const checkboxes = document.querySelectorAll('.file-checkbox');
      checkboxes.forEach(cb => {
        cb.checked = checked;
        toggleFileSelection(cb, true); // Skip individual updates
      });
      updateDownloadButton();
    }

    // Toggle selection of a single file
    function toggleFileSelection(checkbox, skipUpdate) {
      const filePath = checkbox.dataset.file;
      if (checkbox.checked) {
        selectedFiles.add(filePath);
      } else {
        selectedFiles.delete(filePath);
      }
      
      // Sync the corresponding checkbox in the other view
      const isGridCheckbox = checkbox.id.startsWith('file_grid_');
      const idx = checkbox.id.split('_').pop();
      
      if (isGridCheckbox) {
        const listCheckbox = document.getElementById(`file_list_${idx}`);
        if (listCheckbox) listCheckbox.checked = checkbox.checked;
      } else {
        const gridCheckbox = document.getElementById(`file_grid_${idx}`);
        if (gridCheckbox) gridCheckbox.checked = checkbox.checked;
      }
      
      if (!skipUpdate) {
        updateDownloadButton();
        
        // Update "select all" checkbox
        const selectAllCheckbox = document.getElementById('selectAllFiles');
        const totalCheckboxes = document.querySelectorAll('.file-checkbox').length;
        selectAllCheckbox.checked = (totalCheckboxes > 0 && selectedFiles.size === totalCheckboxes);
      }
    }

    // Update the download button based on selection state
    function updateDownloadButton() {
      const downloadBtn = document.getElementById('downloadSelectedBtn');
      if (selectedFiles.size > 0) {
        downloadBtn.disabled = false;
        downloadBtn.classList.remove('opacity-50', 'cursor-not-allowed');
        downloadBtn.querySelector('span').textContent = `Download (${selectedFiles.size})`;
      } else {
        downloadBtn.disabled = true;
        downloadBtn.classList.add('opacity-50', 'cursor-not-allowed');
        downloadBtn.querySelector('span').textContent = 'Download Selected';
      }
    }

    // Download a single file
    function downloadSingleFile(filePath) {
      // Using a direct window.location approach for simple downloads
      window.location.href = `/download?file=${filePath}`;
    }

    // Download multiple selected files
    function downloadSelected() {
      if (selectedFiles.size === 0) {
        showToast('No files selected', 'error');
        return;
      }

      if (selectedFiles.size === 1) {
        // If only one file is selected, use the single file download
        downloadSingleFile(Array.from(selectedFiles)[0]);
        return;
      }

      // For multiple files, construct the URL with all selected files
      const filesParam = Array.from(selectedFiles).join(',');
      window.location.href = `/download-multiple?files=${filesParam}`;
    }

    // Refresh file list
    function refreshFiles() {
      loadFiles(currentPath);
    }
  </script>
</body>
</html>