MovieMapper/renderer.js

402 lines
12 KiB
JavaScript

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 = '<p>No media files found in this directory.</p>';
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 ? '<span class="problematic-label">⚠️</span> ' : '';
fileItem.innerHTML = `
<div class="file-name" data-file-path="${file.path}">${problemLabel}${file.name}</div>
<div class="file-duration">${duration}</div>
<div class="file-quality">${quality}</div>
<div class="file-fps">${fps}</div>
`;
// 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 = `<p>Error: ${error.message}</p>`;
}
}
// 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 = '<p>No shows found.</p>';
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
displaySeasons(currentSeasons);
} else {
console.error('Failed to fetch show details:', result.error);
document.getElementById('seasons-container').innerHTML = '<p>Error loading seasons: ' + (result.error || 'Unknown error') + '</p>';
}
} catch (error) {
console.error('Error fetching show details:', error);
document.getElementById('seasons-container').innerHTML = '<p>Error loading seasons: ' + error.message + '</p>';
}
}
// Display seasons
function displaySeasons(seasons) {
const seasonsContainer = document.getElementById('seasons-container');
seasonsContainer.innerHTML = '';
if (!seasons || seasons.length === 0) {
seasonsContainer.innerHTML = '<p>No seasons available.</p>';
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 = '8px';
seasonItem.style.margin = '5px 0';
seasonItem.style.border = '1px solid #ddd';
seasonItem.style.borderRadius = '4px';
seasonItem.style.cursor = 'pointer';
seasonItem.style.backgroundColor = '#f8f9fa';
// Format season display
let seasonDisplay = `Season ${season.number}`;
if (season.name && season.name !== `Season ${season.number}`) {
seasonDisplay += ` - ${season.name}`;
}
if (season.type) {
seasonDisplay += ` (${season.type})`;
}
if (season.episodeCount) {
seasonDisplay += ` (${season.episodeCount} episodes)`;
}
seasonItem.textContent = seasonDisplay;
// 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 {
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 = '<p>Error loading episodes: ' + (result.error || 'Unknown error') + '</p>';
}
} catch (error) {
console.error('Error fetching episodes:', error);
document.getElementById('seasons-container').innerHTML = '<p>Error loading episodes: ' + error.message + '</p>';
}
}
// Display episodes
function displayEpisodes(episodes) {
const seasonsContainer = document.getElementById('seasons-container');
seasonsContainer.innerHTML = '';
if (!episodes || episodes.length === 0) {
seasonsContainer.innerHTML = '<p>No episodes available.</p>';
return;
}
// Create a container for episodes
const episodesList = document.createElement('div');
episodesList.style.marginTop = '10px';
episodes.forEach(episode => {
const episodeItem = document.createElement('div');
episodeItem.className = 'episode-item';
episodeItem.style.padding = '8px';
episodeItem.style.margin = '5px 0';
episodeItem.style.border = '1px solid #ddd';
episodeItem.style.borderRadius = '4px';
episodeItem.style.cursor = 'pointer';
episodeItem.style.backgroundColor = '#fff';
// Format episode display
let episodeDisplay = `Episode ${episode.number}: ${episode.name || 'Untitled'}`;
if (episode.aired) {
episodeDisplay += ` (${episode.aired})`;
}
if (episode.runtime) {
episodeDisplay += ` (${episode.runtime} min)`;
}
episodeItem.textContent = episodeDisplay;
// Add click event (could show more details)
episodeItem.addEventListener('click', () => {
alert(`Episode: ${episode.name || 'Untitled'}\nRuntime: ${episode.runtime || 'N/A'} min\nAired: ${episode.aired || 'N/A'}`);
});
episodesList.appendChild(episodeItem);
});
seasonsContainer.appendChild(episodesList);
}
// 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');