MovieMapper/renderer.js

264 lines
7.8 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');
// Current state
let currentDirectory = null;
let currentFiles = [];
// 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
function selectShow(show) {
console.log('Selected show:', show);
// In a real implementation, this would fetch show details and display seasons
alert(`Selected show: ${show.seriesName}\nStatus: ${show.status || 'N/A'}\nFirst Aired: ${show.firstAired || 'N/A'}`);
}
// 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');