const { ipcRenderer } = require('electron'); const path = require('path'); // DOM Elements const selectDirBtn = document.getElementById('select-dir-btn'); const selectedDirEl = document.getElementById('selected-dir'); const searchInput = document.getElementById('search-input'); const searchResultsEl = document.getElementById('search-results'); const fileListEl = document.getElementById('file-list'); const showDetailsEl = document.getElementById('show-details'); // Current state let currentDirectory = null; let currentFiles = []; let currentShow = null; let currentSeasons = []; let currentEpisodes = []; // Event Listeners selectDirBtn.addEventListener('click', selectDirectory); searchInput.addEventListener('input', debounce(searchShows, 300)); document.getElementById('test-api-btn').addEventListener('click', testTVDBAPI); // Select directory function async function selectDirectory() { try { // Use IPC to handle directory selection const result = await ipcRenderer.invoke('select-directory'); if (result.success) { const directory = result.directory; currentDirectory = directory; selectedDirEl.textContent = `Selected: ${directory}`; // Scan the directory for media files await scanDirectory(directory); } else { throw new Error(result.error); } } catch (error) { console.error('Error selecting directory:', error); alert(`Error: ${error.message}`); } } // Scan directory for media files async function scanDirectory(directoryPath) { try { const result = await ipcRenderer.invoke('scan-directory', directoryPath); if (result.success) { currentFiles = result.files; displayFiles(currentFiles); } else { throw new Error(result.error); } } catch (error) { console.error('Error scanning directory:', error); alert(`Error scanning directory: ${error.message}`); } } // Display files in the UI function displayFiles(files) { fileListEl.innerHTML = ''; if (files.length === 0) { fileListEl.innerHTML = '

No media files found in this directory.

'; return; } files.forEach(file => { const fileItem = document.createElement('div'); fileItem.className = 'file-item'; // 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 = `
${problemLabel}${file.name}
${duration}
${quality}
${fps}
`; // Add click event to make file name editable const fileNameElement = fileItem.querySelector('.file-name'); fileNameElement.addEventListener('click', function(e) { makeEditable(e.target); }); fileListEl.appendChild(fileItem); }); } // Make a file name editable function makeEditable(element) { // Prevent editing if already in edit mode if (element.contentEditable === 'true') return; // Store original text const originalText = element.textContent; // Make element editable element.contentEditable = 'true'; element.focus(); element.classList.add('editing'); // Select all text when editing starts const range = document.createRange(); range.selectNodeContents(element); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); // Handle saving when user finishes editing const saveEdit = async function() { if (element.textContent.trim() !== originalText.trim()) { // Send request to main process to rename the file try { const result = await ipcRenderer.invoke('rename-file', { oldPath: element.dataset.filePath, newName: element.textContent.trim() }); if (result.success) { console.log('File renamed successfully:', result.message); // Update the file name in the UI to reflect the change element.textContent = element.textContent.trim(); } else { console.error('Failed to rename file:', result.error); // Revert to original name on failure element.textContent = originalText; } } catch (error) { console.error('Error renaming file:', error); // Revert to original name on error element.textContent = originalText; } } // Clean up element.contentEditable = 'false'; element.classList.remove('editing'); }; // Save on Enter key or blur element.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); saveEdit(); } }); element.addEventListener('blur', saveEdit); } // Search shows function async function searchShows() { const query = searchInput.value.trim(); if (query.length < 2) { searchResultsEl.innerHTML = ''; return; } try { const result = await ipcRenderer.invoke('search-tvdb', query); if (result.success) { displaySearchResults(result.results); } else { throw new Error(result.error); } } catch (error) { console.error('Error searching shows:', error); searchResultsEl.innerHTML = `

Error: ${error.message}

`; } } // Test TVDB API connectivity async function testTVDBAPI() { try { const result = await ipcRenderer.invoke('test-tvdb-api'); console.log('TVDB API Test:', result); if (result.success) { console.log('TVDB API is working:', result.message); alert('TVDB API Test Successful!\n' + result.message); } else { console.error('TVDB API test failed:', result.error); alert('TVDB API Test Failed!\n' + result.error); } } catch (error) { console.error('Error testing TVDB API:', error); alert('Error testing TVDB API: ' + error.message); } } // Add a test button to the UI (you can add this to the HTML or call it manually) // For now, we'll just make it available in the console console.log('TVDB API test function available: testTVDBAPI()'); // Debug function to log problematic file info async function debugProblematicFile(filePath) { try { const result = await ipcRenderer.invoke('log-file-info', filePath); if (result.success) { console.log('File info:', result.fileInfo); } else { console.error('Failed to get file info:', result.error); } } catch (error) { console.error('Error debugging file:', error); } } // Display search results function displaySearchResults(results) { searchResultsEl.innerHTML = ''; if (results.length === 0) { searchResultsEl.innerHTML = '

No shows found.

'; return; } results.forEach(show => { const resultItem = document.createElement('div'); resultItem.className = 'search-result-item'; // Create a more detailed display let resultText = show.seriesName || show.name; if (show.firstAired) { resultText += ` (${show.firstAired})`; } resultItem.textContent = resultText; resultItem.addEventListener('click', () => selectShow(show)); searchResultsEl.appendChild(resultItem); }); } // Select a show and display more details async function selectShow(show) { console.log('Selected show:', show); currentShow = show; // Display show title document.getElementById('show-title').textContent = show.seriesName || show.name; // Fetch show details including seasons try { const result = await ipcRenderer.invoke('get-show-details', show.id); if (result.success && result.data) { const showData = result.data; currentSeasons = showData.seasons || []; // Display seasons - since we're not getting seasons from the API directly, // we'll show a message indicating we need to fetch seasons separately if (currentSeasons.length === 0) { document.getElementById('seasons-container').innerHTML = '

Seasons will be loaded when you click on a season.

'; } else { displaySeasons(currentSeasons); } } else { console.error('Failed to fetch show details:', result.error); document.getElementById('seasons-container').innerHTML = '

Error loading seasons: ' + (result.error || 'Unknown error') + '

'; } } catch (error) { console.error('Error fetching show details:', error); document.getElementById('seasons-container').innerHTML = '

Error loading seasons: ' + error.message + '

'; } } // Display seasons with episode counts function displaySeasons(seasons) { const seasonsContainer = document.getElementById('seasons-container'); seasonsContainer.innerHTML = ''; if (!seasons || seasons.length === 0) { seasonsContainer.innerHTML = '

No seasons available.

'; return; } // Create a container for seasons const seasonsList = document.createElement('div'); seasonsList.style.marginTop = '10px'; seasons.forEach(season => { const seasonItem = document.createElement('div'); seasonItem.className = 'season-item'; seasonItem.style.padding = '12px'; seasonItem.style.margin = '8px 0'; seasonItem.style.border = '1px solid #ddd'; seasonItem.style.borderRadius = '6px'; seasonItem.style.cursor = 'pointer'; seasonItem.style.backgroundColor = '#f8f9fa'; seasonItem.style.display = 'flex'; seasonItem.style.justifyContent = 'space-between'; seasonItem.style.alignItems = 'center'; // Format season display with episode count let seasonDisplay = `Season ${season.number}`; if (season.type && season.type !== 'Unknown') { seasonDisplay += ` (${season.type})`; } const episodeCount = season.episodeCount || 0; seasonItem.innerHTML = ` ${seasonDisplay} ${episodeCount} episodes `; // Add click event to fetch episodes seasonItem.addEventListener('click', () => { fetchAndDisplayEpisodes(currentShow.id, season.number); }); seasonsList.appendChild(seasonItem); }); seasonsContainer.appendChild(seasonsList); } // Fetch and display episodes for a season async function fetchAndDisplayEpisodes(showId, seasonNumber) { try { // Show loading message while fetching document.getElementById('seasons-container').innerHTML = '

Loading episodes...

'; const result = await ipcRenderer.invoke('get-season-episodes', showId, seasonNumber); if (result.success && result.data) { const episodes = result.data.episodes || []; displayEpisodes(episodes); } else { console.error('Failed to fetch episodes:', result.error); document.getElementById('seasons-container').innerHTML = '

Error loading episodes: ' + (result.error || 'Unknown error') + '

'; } } catch (error) { console.error('Error fetching episodes:', error); // Show a more user-friendly error message document.getElementById('seasons-container').innerHTML = `

Error loading episodes: ${error.message || 'Failed to load episodes'}

Note: This may be due to API limitations with the TVDB v4 API.

`; } } // Display episodes in clean table format function displayEpisodes(episodes) { const seasonsContainer = document.getElementById('seasons-container'); seasonsContainer.innerHTML = ''; if (!episodes || episodes.length === 0) { seasonsContainer.innerHTML = '

No episodes available.

'; return; } // Create a container for episodes table const episodesTable = document.createElement('div'); episodesTable.style.marginTop = '10px'; episodesTable.style.border = '1px solid #ddd'; episodesTable.style.borderRadius = '6px'; episodesTable.style.overflow = 'hidden'; // Create table header const tableHeader = document.createElement('div'); tableHeader.style.display = 'grid'; tableHeader.style.gridTemplateColumns = '1fr 1fr'; tableHeader.style.backgroundColor = '#f8f9fa'; tableHeader.style.padding = '10px'; tableHeader.style.fontWeight = 'bold'; tableHeader.style.borderBottom = '1px solid #ddd'; tableHeader.innerHTML = `
Episode Name
Runtime
`; episodesTable.appendChild(tableHeader); episodes.forEach(episode => { const episodeRow = document.createElement('div'); episodeRow.className = 'episode-item'; episodeRow.style.display = 'grid'; episodeRow.style.gridTemplateColumns = '1fr 1fr'; episodeRow.style.padding = '10px'; episodeRow.style.borderBottom = '1px solid #eee'; episodeRow.style.backgroundColor = '#fff'; episodeRow.style.cursor = 'pointer'; episodeRow.style.transition = 'background-color 0.2s'; episodeRow.addEventListener('mouseenter', () => { episodeRow.style.backgroundColor = '#f5f5f5'; }); episodeRow.addEventListener('mouseleave', () => { episodeRow.style.backgroundColor = '#fff'; }); // Format episode display const episodeName = episode.name || 'Untitled'; const episodeRuntime = episode.runtime ? `${episode.runtime} min` : 'N/A'; episodeRow.innerHTML = `
Episode ${episode.number}: ${episodeName}
${episodeRuntime}
`; // Add click event (could show more details) episodeRow.addEventListener('click', () => { alert(`Episode: ${episode.name || 'Untitled'}\nRuntime: ${episode.runtime || 'N/A'} min\nAired: ${episode.aired || 'N/A'}`); }); episodesTable.appendChild(episodeRow); }); seasonsContainer.appendChild(episodesTable); } // Debounce function for search input function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Initialize the application console.log('Movie Mapper application initialized');