/** * FileListManager - Handles file list display and manipulation */ class FileListManager { constructor(fileListEl) { this.fileListEl = fileListEl; this.draggedItem = null; } /** * Display files in the UI * @param {Array} files - Array of file objects */ displayFiles(files) { this.fileListEl.innerHTML = ''; if (files.length === 0) { this.fileListEl.innerHTML = '

No media files found in this directory.

'; return; } // Separate folders from media files const folders = files.filter(f => f.isFolder); const mediaFiles = files.filter(f => !f.isFolder); // Track episode number for media files only let episodeNumber = 1; // Display folders first (not draggable, no episode number) folders.forEach(file => { this._createFolderElement(file); }); // Display media files with episode numbers and drag/drop mediaFiles.forEach((file, index) => { this._createMediaFileElement(file, episodeNumber, index); episodeNumber++; }); } /** * Create folder element * @param {Object} file - File object * @private */ _createFolderElement(file) { const fileItem = document.createElement('div'); fileItem.className = 'file-item folder-item'; fileItem.innerHTML = `
📁
${file.name}
`; // Add click handler to navigate into folder fileItem.addEventListener('click', () => { // This will be handled by the caller }); this.fileListEl.appendChild(fileItem); } /** * Create media file element * @param {Object} file - File object * @param {number} episodeNumber - Episode number * @param {number} index - File index */ _createMediaFileElement(file, episodeNumber, index) { const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.draggable = true; fileItem.dataset.index = index; fileItem.dataset.filePath = file.path; // Use actual duration from file metadata const duration = file.duration || '00:00'; const quality = file.quality || 'unknown'; const fps = file.fps || 'unknown'; // Add label for problematic files const problemLabel = file.isProblematic ? '⚠️ ' : ''; fileItem.innerHTML = `
⋮⋮
${episodeNumber}
${file.name}
${duration}
${quality}
${fps}
🏷️ 💬 🗑️ 🎬
`; // Add drag and drop event listeners fileItem.addEventListener('dragstart', this._handleDragStart.bind(this)); fileItem.addEventListener('dragend', this._handleDragEnd.bind(this)); fileItem.addEventListener('dragover', this._handleDragOver.bind(this)); fileItem.addEventListener('dragleave', this._handleDragLeave.bind(this)); fileItem.addEventListener('drop', this._handleDrop.bind(this)); this.fileListEl.appendChild(fileItem); } /** * Handle drag start * @param {Event} e - Drag event * @private */ _handleDragStart(e) { const fileItem = this; this.draggedItem = fileItem; fileItem.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', fileItem.dataset.index); } /** * Handle drag end * @param {Event} e - Drag event * @private */ _handleDragEnd(e) { const fileItem = this; fileItem.classList.remove('dragging'); // Remove drag-over class from all items document.querySelectorAll('.file-item').forEach(item => { item.classList.remove('drag-over'); }); this.draggedItem = null; } /** * Handle drag over * @param {Event} e - Drag event * @private */ _handleDragOver(e) { const fileItem = this; e.preventDefault(); e.dataTransfer.dropEffect = 'move'; // Only show drag-over for non-folder items if (!fileItem.classList.contains('folder-item') && fileItem !== this.draggedItem) { fileItem.classList.add('drag-over'); } } /** * Handle drag leave * @param {Event} e - Drag event * @private */ _handleDragLeave(e) { const fileItem = this; fileItem.classList.remove('drag-over'); } /** * Handle drop * @param {Event} e - Drop event */ _handleDrop(e) { const fileItem = this; e.preventDefault(); fileItem.classList.remove('drag-over'); if (fileItem === this.draggedItem || fileItem.classList.contains('folder-item')) return; // Get all media file items (not folders) const fileItems = Array.from(this.fileListEl.querySelectorAll('.file-item:not(.folder-item)')); const draggedIndex = fileItems.indexOf(this.draggedItem); const dropIndex = fileItems.indexOf(fileItem); if (draggedIndex === -1 || dropIndex === -1) return; // Move the dragged item in the DOM if (draggedIndex < dropIndex) { fileItem.parentNode.insertBefore(this.draggedItem, fileItem.nextSibling); } else { fileItem.parentNode.insertBefore(this.draggedItem, fileItem); } // Update episode numbers this.updateEpisodeNumbers(); } /** * Update episode numbers */ updateEpisodeNumbers() { const fileItems = this.fileListEl.querySelectorAll('.file-item:not(.folder-item)'); let episodeNum = 1; fileItems.forEach((item, index) => { const episodeEl = item.querySelector('.episode-number'); if (episodeEl) { const storedStart = episodeEl.dataset.episodeStart; const storedEnd = episodeEl.dataset.episodeEnd; const start = storedStart ? parseInt(storedStart) : episodeNum; const end = storedEnd ? parseInt(storedEnd) : start; const rangeText = start === end ? `${start}` : `${start}-${end}`; episodeEl.textContent = rangeText; episodeEl.dataset.episode = episodeNum; episodeNum = end + 1; } }); } /** * Clear the file list */ clear() { this.fileListEl.innerHTML = ''; } /** * Get all media file items * @returns {NodeList} Media file items */ getMediaFileItems() { return this.fileListEl.querySelectorAll('.file-item:not(.folder-item)'); } /** * Get folder items * @returns {NodeList} Folder items */ getFolderItems() { return this.fileListEl.querySelectorAll('.folder-item'); } } module.exports = FileListManager;