913 lines
31 KiB
JavaScript
913 lines
31 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}">${file.name}</div>
|
|
<div class="file-duration">${duration}</div>
|
|
<div class="file-quality">${quality}</div>
|
|
<div class="file-fps">${fps}</div>
|
|
<div class="file-tags">
|
|
<span class="tag-icon extra-tag" data-file-path="${file.path}" title="Mark as Extra">🏷️</span>
|
|
<span class="tag-icon commentary-tag" data-file-path="${file.path}" title="Add Commentary">💬</span>
|
|
<span class="video-preview-btn" data-file-path="${file.path}" title="Preview Video">🎬</span>
|
|
<button class="play-button" style="opacity: 0.3; cursor: default; flex-shrink: 0;" data-file-path="${file.path}" disabled>▶️</button>
|
|
</div>
|
|
`;
|
|
|
|
// Add click event to make file name editable
|
|
const fileNameElement = fileItem.querySelector('.file-name');
|
|
fileNameElement.addEventListener('click', function(e) {
|
|
makeEditable(e.target);
|
|
});
|
|
|
|
// Add hover effects for tags
|
|
const tagIcons = fileItem.querySelectorAll('.tag-icon');
|
|
tagIcons.forEach(icon => {
|
|
icon.addEventListener('mouseenter', function() {
|
|
this.style.opacity = '1';
|
|
this.style.transform = 'scale(1.1)';
|
|
});
|
|
|
|
icon.addEventListener('mouseleave', function() {
|
|
this.style.opacity = '0.7';
|
|
this.style.transform = 'scale(1)';
|
|
});
|
|
|
|
// Add click handlers for tagging
|
|
icon.addEventListener('click', function(e) {
|
|
e.stopPropagation(); // Prevent event bubbling
|
|
const filePath = this.dataset.filePath;
|
|
const tagType = this.classList.contains('extra-tag') ? 'extra' : 'commentary';
|
|
|
|
// Check if file is already tagged with this type
|
|
const fileItem = this.closest('.file-item');
|
|
const isTagged = fileItem.hasAttribute('data-tagged-' + tagType);
|
|
|
|
if (isTagged) {
|
|
// Untag the file
|
|
untagFile(filePath, tagType);
|
|
} else {
|
|
// If tagging this type, untag any existing tag of the other type
|
|
const otherTagType = tagType === 'extra' ? 'commentary' : 'extra';
|
|
if (fileItem.hasAttribute('data-tagged-' + otherTagType)) {
|
|
untagFile(filePath, otherTagType);
|
|
}
|
|
// Tag the file
|
|
addTagToEpisode(filePath, tagType);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add click handler for preview button
|
|
const previewBtn = fileItem.querySelector('.video-preview-btn');
|
|
previewBtn.addEventListener('click', function(e) {
|
|
e.stopPropagation(); // Prevent event bubbling
|
|
const filePath = this.dataset.filePath;
|
|
openVideoPreview(filePath);
|
|
});
|
|
|
|
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();
|
|
|
|
// Also update the data attribute to reflect the new path
|
|
const oldPath = element.dataset.filePath;
|
|
const newPath = oldPath.substring(0, oldPath.lastIndexOf(path.sep) + 1) + element.textContent.trim();
|
|
element.dataset.filePath = newPath;
|
|
} 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);
|
|
}
|
|
|
|
// Function to handle tagging
|
|
function addTagToEpisode(filePath, tagType) {
|
|
console.log(`Tagging file ${filePath} as ${tagType}`);
|
|
|
|
// Find the file item in the UI
|
|
const fileItems = document.querySelectorAll('.file-item');
|
|
fileItems.forEach(item => {
|
|
const fileNameElement = item.querySelector('.file-name');
|
|
if (fileNameElement && fileNameElement.dataset.filePath === filePath) {
|
|
// Add visual indication of tagging using data attribute for CSS styling
|
|
const tagIcon = item.querySelector(`.${tagType}-tag`);
|
|
if (tagIcon) {
|
|
// Make the icon fully saturated and highlight in bright yellow with very strong visual effect
|
|
tagIcon.style.opacity = '1';
|
|
tagIcon.style.filter = 'none';
|
|
tagIcon.style.color = tagType === 'extra' ? '#FFD700' : '#17a2b8'; // Brighter yellow or teal color
|
|
tagIcon.style.textShadow = '0 0 15px rgba(255, 215, 0, 1)'; // Very bright yellow glow (for extra tags)
|
|
tagIcon.style.transform = 'scale(1.3)'; // More pronounced enlargement
|
|
tagIcon.style.boxShadow = '0 0 10px rgba(255, 215, 0, 0.8)'; // Additional glow effect
|
|
|
|
// Add a data attribute to track that this file is tagged
|
|
item.setAttribute('data-tagged-' + tagType, 'true');
|
|
|
|
// Enable the play button
|
|
const playButton = item.querySelector('.play-button');
|
|
if (playButton) {
|
|
playButton.style.opacity = '1';
|
|
playButton.style.cursor = 'pointer';
|
|
playButton.disabled = false;
|
|
playButton.style.pointerEvents = 'auto';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update the tagged count display
|
|
updateTaggedCount();
|
|
|
|
// In a real implementation, this would save to a database or file
|
|
// For now, we'll just log to console
|
|
console.log(`File ${filePath} tagged as ${tagType} - would be saved in real implementation`);
|
|
}
|
|
|
|
// Function to move tagged files to appropriate folders
|
|
function moveTaggedFile(filePath, tagType) {
|
|
console.log(`Moving file ${filePath} to ${tagType} folder`);
|
|
|
|
// Send request to main process to move the file
|
|
ipcRenderer.invoke('move-file-to-folder', {
|
|
filePath: filePath,
|
|
folderName: tagType
|
|
}).then(result => {
|
|
if (result.success) {
|
|
console.log(`File moved successfully to ${tagType} folder`);
|
|
// Optional: Update UI to reflect that the file has been moved
|
|
} else {
|
|
console.error(`Failed to move file: ${result.error}`);
|
|
alert(`Failed to move file: ${result.error}`);
|
|
}
|
|
}).catch(error => {
|
|
console.error('Error moving file:', error);
|
|
alert(`Error moving file: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
// Function to handle untagging
|
|
function untagFile(filePath, tagType) {
|
|
console.log(`Untagging file ${filePath} from ${tagType}`);
|
|
|
|
// Find the file item in the UI
|
|
const fileItems = document.querySelectorAll('.file-item');
|
|
fileItems.forEach(item => {
|
|
const fileNameElement = item.querySelector('.file-name');
|
|
if (fileNameElement && fileNameElement.dataset.filePath === filePath) {
|
|
// Remove visual indication of tagging
|
|
const tagIcon = item.querySelector(`.${tagType}-tag`);
|
|
if (tagIcon) {
|
|
// Reset to original appearance
|
|
tagIcon.style.opacity = '0.7';
|
|
tagIcon.style.filter = 'none';
|
|
tagIcon.style.color = ''; // Reset to default color
|
|
tagIcon.style.textShadow = 'none';
|
|
tagIcon.style.transform = 'scale(1)';
|
|
tagIcon.style.boxShadow = 'none';
|
|
|
|
// Remove the data attribute
|
|
item.removeAttribute('data-tagged-' + tagType);
|
|
|
|
// Disable the play button when untagging
|
|
const playButton = item.querySelector('.play-button');
|
|
if (playButton) {
|
|
playButton.style.opacity = '0.3';
|
|
playButton.style.cursor = 'default';
|
|
playButton.disabled = true;
|
|
playButton.style.pointerEvents = 'none';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update the tagged count display
|
|
updateTaggedCount();
|
|
|
|
// In a real implementation, this would remove from database or file
|
|
// For now, we'll just log to console
|
|
console.log(`File ${filePath} untagged from ${tagType} - would be removed in real implementation`);
|
|
}
|
|
|
|
// Function to update the tagged items count display
|
|
function updateTaggedCount() {
|
|
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary]');
|
|
const count = taggedItems.length;
|
|
const taggedCircle = document.getElementById('tagged-circle');
|
|
const taggedCount = document.getElementById('tagged-count');
|
|
|
|
if (taggedCount) {
|
|
taggedCount.textContent = count;
|
|
}
|
|
|
|
// Animate the circle position based on count
|
|
if (taggedCircle) {
|
|
if (count === 0) {
|
|
// Move down out of sight
|
|
taggedCircle.style.transform = 'translateY(100px)';
|
|
taggedCircle.style.bottom = '-100px';
|
|
} else {
|
|
// Move up to show count (appears at the same spot at bottom right)
|
|
taggedCircle.style.transform = 'translateY(0)';
|
|
taggedCircle.style.bottom = '20px';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize tagged count on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updateTaggedCount();
|
|
});
|
|
|
|
// 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>`;
|
|
}
|
|
}
|
|
|
|
// Clear search results when a show is selected
|
|
function clearSearchResults() {
|
|
searchResultsEl.innerHTML = '';
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Clear search results when a show is selected
|
|
clearSearchResults();
|
|
|
|
// 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 = '<p>Seasons will be loaded when you click on a season.</p>';
|
|
} else {
|
|
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 with episode counts
|
|
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 = '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 = `
|
|
<span>${seasonDisplay}</span>
|
|
<span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;">
|
|
${episodeCount} episodes
|
|
</span>
|
|
`;
|
|
|
|
// 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 = '<p>Loading episodes...</p>';
|
|
|
|
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);
|
|
// Show a more user-friendly error message
|
|
document.getElementById('seasons-container').innerHTML = `
|
|
<div style="padding: 10px; background-color: #f8d7da; color: #721c24; border-radius: 4px;">
|
|
<p>Error loading episodes: ${error.message || 'Failed to load episodes'}</p>
|
|
<p style="font-size: 12px; margin-top: 5px;">Note: This may be due to API limitations with the TVDB v4 API.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Display episodes in clean table format
|
|
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 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 = `
|
|
<div>Episode Name</div>
|
|
<div>Runtime</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div style="font-weight: 500;">Episode ${episode.number}: ${episodeName}</div>
|
|
<div style="color: #666;">${episodeRuntime}</div>
|
|
`;
|
|
|
|
// 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);
|
|
|
|
// Also update the main file list to show episode matching information
|
|
updateFileListWithEpisodeInfo(episodes);
|
|
}
|
|
|
|
// Enhanced function to create a more integrated view
|
|
function createIntegratedEpisodeFileView(episodes) {
|
|
// This function would create a more integrated view showing both files and episodes
|
|
// For now, we're enhancing the existing functionality to better align the views
|
|
|
|
// Add a section that shows how files might align with episodes
|
|
const fileListContainer = document.getElementById('file-list');
|
|
|
|
// Create a section showing the relationship
|
|
const relationshipSection = document.createElement('div');
|
|
relationshipSection.style.marginTop = '15px';
|
|
relationshipSection.style.padding = '12px';
|
|
relationshipSection.style.backgroundColor = '#fff8e1';
|
|
relationshipSection.style.border = '1px solid #ffd54f';
|
|
relationshipSection.style.borderRadius = '6px';
|
|
relationshipSection.innerHTML = `
|
|
<h4 style="margin-top: 0; color: #ff9800;">File-Episode Relationship</h4>
|
|
<p style="font-size: 14px; margin: 5px 0;">
|
|
<strong>Matching Strategy:</strong> Files are matched to episodes based on naming patterns.
|
|
</p>
|
|
<p style="font-size: 14px; margin: 5px 0;">
|
|
<strong>Example:</strong> "Show.S01E01.Title.mp4" matches Episode 1 of Season 1.
|
|
</p>
|
|
<p style="font-size: 14px; margin: 5px 0;">
|
|
<strong>Status:</strong> ${currentFiles.length} files in directory, ${episodes.length} episodes available.
|
|
</p>
|
|
`;
|
|
|
|
// Insert this section after the episode info
|
|
const episodeInfo = fileListContainer.querySelector('.episode-info');
|
|
if (episodeInfo) {
|
|
fileListContainer.insertBefore(relationshipSection, episodeInfo.nextSibling);
|
|
}
|
|
}
|
|
|
|
// Update file list to show episode matching information
|
|
function updateFileListWithEpisodeInfo(episodes) {
|
|
// When we have episodes, we want to show basic episode information
|
|
if (currentFiles && currentFiles.length > 0 && episodes && episodes.length > 0) {
|
|
console.log('Updating file list with episode info for', episodes.length, 'episodes');
|
|
|
|
// Create a simple note about episode availability
|
|
const fileListContainer = document.getElementById('file-list');
|
|
|
|
// Add a simple note about episodes
|
|
const episodeNote = document.createElement('div');
|
|
episodeNote.style.marginTop = '15px';
|
|
episodeNote.style.padding = '10px';
|
|
episodeNote.style.backgroundColor = '#e7f3ff';
|
|
episodeNote.style.border = '1px solid #b3d9ff';
|
|
episodeNote.style.borderRadius = '5px';
|
|
episodeNote.style.fontSize = '14px';
|
|
episodeNote.innerHTML = `
|
|
<strong>Episode Information:</strong>
|
|
${episodes.length} episodes available for this show.
|
|
<br><small>Episode details are displayed in the sidebar.</small>
|
|
`;
|
|
|
|
// Add this note to the file list container
|
|
if (fileListContainer.firstChild) {
|
|
fileListContainer.insertBefore(episodeNote, fileListContainer.firstChild);
|
|
} else {
|
|
fileListContainer.appendChild(episodeNote);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
};
|
|
}
|
|
|
|
// Modal element for video preview
|
|
let videoPreviewModal = null;
|
|
|
|
// Initialize the application
|
|
console.log('Movie Mapper application initialized');
|
|
|
|
// Function to open video preview modal
|
|
function openVideoPreview(filePath) {
|
|
console.log('Opening video preview for:', filePath);
|
|
|
|
// Create modal if it doesn't exist
|
|
if (!videoPreviewModal) {
|
|
createVideoPreviewModal();
|
|
}
|
|
|
|
// Show the modal
|
|
videoPreviewModal.style.display = 'block';
|
|
|
|
// Load video content
|
|
loadVideoPreview(filePath);
|
|
}
|
|
|
|
// Function to create video preview modal
|
|
function createVideoPreviewModal() {
|
|
// Create modal container
|
|
videoPreviewModal = document.createElement('div');
|
|
videoPreviewModal.id = 'video-preview-modal';
|
|
videoPreviewModal.style.cssText = `
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.9);
|
|
z-index: 1000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: auto;
|
|
`;
|
|
|
|
// Create modal content
|
|
const modalContent = document.createElement('div');
|
|
modalContent.style.cssText = `
|
|
position: relative;
|
|
max-width: 90%;
|
|
max-height: 90%;
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 20px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
|
`;
|
|
|
|
// Create close button
|
|
const closeButton = document.createElement('span');
|
|
closeButton.innerHTML = '×';
|
|
closeButton.style.cssText = `
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 15px;
|
|
font-size: 30px;
|
|
font-weight: bold;
|
|
color: #aaa;
|
|
cursor: pointer;
|
|
transition: color 0.3s;
|
|
`;
|
|
|
|
closeButton.addEventListener('mouseenter', function() {
|
|
this.style.color = '#000';
|
|
});
|
|
|
|
closeButton.addEventListener('click', function() {
|
|
videoPreviewModal.style.display = 'none';
|
|
});
|
|
|
|
// Create video container
|
|
const videoContainer = document.createElement('div');
|
|
videoContainer.id = 'video-preview-container';
|
|
videoContainer.style.cssText = `
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
`;
|
|
|
|
// Create file info display
|
|
const fileInfo = document.createElement('div');
|
|
fileInfo.id = 'video-preview-file-info';
|
|
fileInfo.style.cssText = `
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
padding: 10px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
`;
|
|
|
|
// Create loading indicator
|
|
const loadingIndicator = document.createElement('div');
|
|
loadingIndicator.id = 'video-preview-loading';
|
|
loadingIndicator.textContent = 'Loading video preview...';
|
|
loadingIndicator.style.cssText = `
|
|
text-align: center;
|
|
padding: 20px;
|
|
font-size: 16px;
|
|
color: #666;
|
|
`;
|
|
|
|
// Assemble modal
|
|
modalContent.appendChild(closeButton);
|
|
modalContent.appendChild(fileInfo);
|
|
modalContent.appendChild(videoContainer);
|
|
modalContent.appendChild(loadingIndicator);
|
|
videoPreviewModal.appendChild(modalContent);
|
|
|
|
// Add click outside to close
|
|
videoPreviewModal.addEventListener('click', function(e) {
|
|
if (e.target === videoPreviewModal) {
|
|
videoPreviewModal.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Add to body
|
|
document.body.appendChild(videoPreviewModal);
|
|
}
|
|
|
|
// Function to load video preview
|
|
async function loadVideoPreview(filePath) {
|
|
const fileInfo = document.getElementById('video-preview-file-info');
|
|
const videoContainer = document.getElementById('video-preview-container');
|
|
const loadingIndicator = document.getElementById('video-preview-loading');
|
|
|
|
// Show loading
|
|
loadingIndicator.style.display = 'block';
|
|
videoContainer.innerHTML = '';
|
|
fileInfo.innerHTML = `<strong>Loading:</strong> ${filePath.split('/').pop()}`;
|
|
|
|
try {
|
|
// Check if file exists
|
|
const fs = require('fs');
|
|
if (!fs.existsSync(filePath)) {
|
|
throw new Error('File not found');
|
|
}
|
|
|
|
// Get file stats for info
|
|
const stats = fs.statSync(filePath);
|
|
const fileName = filePath.split('/').pop();
|
|
|
|
// Update file info
|
|
fileInfo.innerHTML = `
|
|
<strong>File:</strong> ${fileName}<br>
|
|
<strong>Size:</strong> ${(stats.size / (1024 * 1024)).toFixed(2)} MB<br>
|
|
<strong>Path:</strong> ${filePath}
|
|
`;
|
|
|
|
// For MKV files, we'll open in default player instead of trying to preview
|
|
const ext = filePath.toLowerCase().split('.').pop();
|
|
if (ext === 'mkv') {
|
|
// For MKV files, show message and provide option to open in default player
|
|
videoContainer.innerHTML = `
|
|
<div style="text-align: center; padding: 20px;">
|
|
<p style="font-size: 16px; color: #666;">
|
|
<strong>Warning:</strong> MKV files are not supported for in-app preview.
|
|
</p>
|
|
<p style="font-size: 14px; color: #888; margin: 10px 0;">
|
|
This file will open in your default media player.
|
|
</p>
|
|
<button id="open-in-player-btn" style="padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
|
|
Open in Default Player
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('open-in-player-btn').addEventListener('click', function() {
|
|
// Send request to main process to open file in default player
|
|
ipcRenderer.invoke('open-file-in-player', filePath);
|
|
});
|
|
} else {
|
|
// For other formats, try to create a video player
|
|
videoContainer.innerHTML = `
|
|
<video id="preview-video" controls style="width: 100%; max-width: 800px; height: auto; margin: 0 auto; display: block;">
|
|
<source src="${filePath}" type="video/mp4">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
`;
|
|
|
|
// Hide loading
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading video preview:', error);
|
|
fileInfo.innerHTML = `<strong>Error:</strong> Could not load preview for ${filePath}`;
|
|
videoContainer.innerHTML = '<p style="color: #dc3545; text-align: center;">Error loading video preview</p>';
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
}
|