Add delete tag, episode count matching indicator, fix season episode counts
This commit is contained in:
parent
ef2fbd1a95
commit
92320cc908
88
index.html
88
index.html
@ -126,7 +126,47 @@
|
|||||||
#seasons-container {
|
#seasons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
border: 1px solid #16213e;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item:hover {
|
||||||
|
background-color: #e94560;
|
||||||
|
border-color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-item.selected {
|
||||||
|
background-color: #0f3460;
|
||||||
|
border-color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
#episodes-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-item {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #0f3460;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-item:hover {
|
||||||
|
background-color: #16213e;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main Content - Files */
|
/* Main Content - Files */
|
||||||
@ -218,13 +258,53 @@
|
|||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
background-color: #16213e;
|
background-color: #16213e;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: background-color 0.2s;
|
transition: all 0.2s;
|
||||||
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-item:hover {
|
.file-item:hover {
|
||||||
background-color: #0f3460;
|
background-color: #0f3460;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.drag-over {
|
||||||
|
border: 2px dashed #e94560;
|
||||||
|
background-color: #0f3460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.folder-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episode-number {
|
||||||
|
min-width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #e94560;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
color: #666;
|
||||||
|
margin-right: 10px;
|
||||||
|
cursor: grab;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle:hover {
|
||||||
|
color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -273,6 +353,10 @@
|
|||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-tag:hover {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
.play-button {
|
.play-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
36
main.js
36
main.js
@ -213,14 +213,20 @@ ipcMain.handle('move-file-to-folder', async (event, { filePath, folderName }) =>
|
|||||||
|
|
||||||
// Validate folder name and map to actual folder names
|
// Validate folder name and map to actual folder names
|
||||||
// "extra" maps to "extras" for Jellyfin compatibility
|
// "extra" maps to "extras" for Jellyfin compatibility
|
||||||
if (folderName !== 'extra' && folderName !== 'commentary') {
|
const validFolders = ['extra', 'commentary', 'delete'];
|
||||||
const error = 'Invalid folder name. Must be "extra" or "commentary"';
|
if (!validFolders.includes(folderName)) {
|
||||||
|
const error = 'Invalid folder name. Must be "extra", "commentary", or "delete"';
|
||||||
writeLog(`ERROR: ${error}`);
|
writeLog(`ERROR: ${error}`);
|
||||||
return { success: false, error: error };
|
return { success: false, error: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map folder names to actual directory names
|
// Map folder names to actual directory names
|
||||||
const actualFolderName = folderName === 'extra' ? 'extras' : 'commentary';
|
let actualFolderName;
|
||||||
|
if (folderName === 'extra') {
|
||||||
|
actualFolderName = 'extras';
|
||||||
|
} else {
|
||||||
|
actualFolderName = folderName; // 'commentary' and 'delete' stay as-is
|
||||||
|
}
|
||||||
|
|
||||||
// Get directory containing the file
|
// Get directory containing the file
|
||||||
const fileDir = path.dirname(filePath);
|
const fileDir = path.dirname(filePath);
|
||||||
@ -421,6 +427,28 @@ ipcMain.handle('get-show-details', async (event, showId) => {
|
|||||||
if (response.data && response.data.data) {
|
if (response.data && response.data.data) {
|
||||||
const showData = response.data.data;
|
const showData = response.data.data;
|
||||||
|
|
||||||
|
// Fetch episodes to count per season
|
||||||
|
let episodeCounts = {};
|
||||||
|
try {
|
||||||
|
const episodesResponse = await axios.get(`https://api4.thetvdb.com/v4/series/${numericId}/episodes/default`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params: { page: 0 }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (episodesResponse.data?.data?.episodes) {
|
||||||
|
episodesResponse.data.data.episodes.forEach(ep => {
|
||||||
|
const sn = ep.seasonNumber;
|
||||||
|
episodeCounts[sn] = (episodeCounts[sn] || 0) + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (epError) {
|
||||||
|
console.log('Could not fetch episodes for count:', epError.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Extract season information properly
|
// Extract season information properly
|
||||||
let seasons = [];
|
let seasons = [];
|
||||||
if (showData.seasons && Array.isArray(showData.seasons)) {
|
if (showData.seasons && Array.isArray(showData.seasons)) {
|
||||||
@ -428,7 +456,7 @@ ipcMain.handle('get-show-details', async (event, showId) => {
|
|||||||
id: season.id,
|
id: season.id,
|
||||||
number: season.number,
|
number: season.number,
|
||||||
type: season.type?.name || 'Unknown',
|
type: season.type?.name || 'Unknown',
|
||||||
episodeCount: season.episodeCount || (season.episodes ? season.episodes.length : 0)
|
episodeCount: episodeCounts[season.number] || 0
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
326
renderer.js
326
renderer.js
@ -18,6 +18,7 @@ let currentFiles = [];
|
|||||||
let currentShow = null;
|
let currentShow = null;
|
||||||
let currentSeasons = [];
|
let currentSeasons = [];
|
||||||
let currentEpisodes = [];
|
let currentEpisodes = [];
|
||||||
|
let selectedSeasonEpisodeCount = 0;
|
||||||
|
|
||||||
// Event Listeners
|
// Event Listeners
|
||||||
selectDirBtn.addEventListener('click', selectDirectory);
|
selectDirBtn.addEventListener('click', selectDirectory);
|
||||||
@ -177,14 +178,17 @@ function displayFiles(files) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
files.forEach(file => {
|
// Separate folders from media files
|
||||||
const fileItem = document.createElement('div');
|
const folders = files.filter(f => f.isFolder);
|
||||||
fileItem.className = 'file-item';
|
const mediaFiles = files.filter(f => !f.isFolder);
|
||||||
|
|
||||||
// Handle folders differently from media files
|
// Track episode number for media files only
|
||||||
if (file.isFolder) {
|
let episodeNumber = 1;
|
||||||
fileItem.classList.add('folder-item');
|
|
||||||
fileItem.style.cursor = 'pointer';
|
// Display folders first (not draggable, no episode number)
|
||||||
|
folders.forEach(file => {
|
||||||
|
const fileItem = document.createElement('div');
|
||||||
|
fileItem.className = 'file-item folder-item';
|
||||||
fileItem.innerHTML = `
|
fileItem.innerHTML = `
|
||||||
<div style="font-size: 18px; margin-right: 10px;">📁</div>
|
<div style="font-size: 18px; margin-right: 10px;">📁</div>
|
||||||
<div class="file-name folder-name" data-file-path="${file.path}">${file.name}</div>
|
<div class="file-name folder-name" data-file-path="${file.path}">${file.name}</div>
|
||||||
@ -200,8 +204,15 @@ function displayFiles(files) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fileListEl.appendChild(fileItem);
|
fileListEl.appendChild(fileItem);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
// Display media files with episode numbers and drag/drop
|
||||||
|
mediaFiles.forEach((file, 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
|
// Use actual duration from file metadata
|
||||||
const duration = file.duration || '00:00';
|
const duration = file.duration || '00:00';
|
||||||
@ -212,6 +223,8 @@ function displayFiles(files) {
|
|||||||
const problemLabel = file.isProblematic ? '<span class="problematic-label">⚠️</span> ' : '';
|
const problemLabel = file.isProblematic ? '<span class="problematic-label">⚠️</span> ' : '';
|
||||||
|
|
||||||
fileItem.innerHTML = `
|
fileItem.innerHTML = `
|
||||||
|
<div class="drag-handle">⋮⋮</div>
|
||||||
|
<div class="episode-number" data-episode="${episodeNumber}">${episodeNumber}</div>
|
||||||
<div class="file-name" data-file-path="${file.path}">${file.name}</div>
|
<div class="file-name" data-file-path="${file.path}">${file.name}</div>
|
||||||
<div class="file-duration">${duration}</div>
|
<div class="file-duration">${duration}</div>
|
||||||
<div class="file-quality">${quality}</div>
|
<div class="file-quality">${quality}</div>
|
||||||
@ -219,14 +232,23 @@ function displayFiles(files) {
|
|||||||
<div class="file-tags">
|
<div class="file-tags">
|
||||||
<span class="tag-icon extra-tag" data-file-path="${file.path}" title="Mark as Extra">🏷️</span>
|
<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="tag-icon commentary-tag" data-file-path="${file.path}" title="Add Commentary">💬</span>
|
||||||
|
<span class="tag-icon delete-tag" data-file-path="${file.path}" title="Mark for Deletion">🗑️</span>
|
||||||
<span class="video-preview-btn" data-file-path="${file.path}" title="Preview Video">🎬</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>
|
<button class="play-button" style="opacity: 0.3; cursor: default; flex-shrink: 0;" data-file-path="${file.path}" disabled>▶️</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Add drag and drop event listeners
|
||||||
|
fileItem.addEventListener('dragstart', handleDragStart);
|
||||||
|
fileItem.addEventListener('dragend', handleDragEnd);
|
||||||
|
fileItem.addEventListener('dragover', handleDragOver);
|
||||||
|
fileItem.addEventListener('dragleave', handleDragLeave);
|
||||||
|
fileItem.addEventListener('drop', handleDrop);
|
||||||
|
|
||||||
// Add click event to make file name editable
|
// Add click event to make file name editable
|
||||||
const fileNameElement = fileItem.querySelector('.file-name');
|
const fileNameElement = fileItem.querySelector('.file-name');
|
||||||
fileNameElement.addEventListener('click', function(e) {
|
fileNameElement.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
makeEditable(e.target);
|
makeEditable(e.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,7 +269,16 @@ function displayFiles(files) {
|
|||||||
icon.addEventListener('click', function(e) {
|
icon.addEventListener('click', function(e) {
|
||||||
e.stopPropagation(); // Prevent event bubbling
|
e.stopPropagation(); // Prevent event bubbling
|
||||||
const filePath = this.dataset.filePath;
|
const filePath = this.dataset.filePath;
|
||||||
const tagType = this.classList.contains('extra-tag') ? 'extra' : 'commentary';
|
let tagType;
|
||||||
|
if (this.classList.contains('extra-tag')) {
|
||||||
|
tagType = 'extra';
|
||||||
|
} else if (this.classList.contains('commentary-tag')) {
|
||||||
|
tagType = 'commentary';
|
||||||
|
} else if (this.classList.contains('delete-tag')) {
|
||||||
|
tagType = 'delete';
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if file is already tagged with this type
|
// Check if file is already tagged with this type
|
||||||
const fileItem = this.closest('.file-item');
|
const fileItem = this.closest('.file-item');
|
||||||
@ -257,11 +288,12 @@ function displayFiles(files) {
|
|||||||
// Untag the file
|
// Untag the file
|
||||||
untagFile(filePath, tagType);
|
untagFile(filePath, tagType);
|
||||||
} else {
|
} else {
|
||||||
// If tagging this type, untag any existing tag of the other type
|
// If tagging this type, untag any existing tag of other types
|
||||||
const otherTagType = tagType === 'extra' ? 'commentary' : 'extra';
|
['extra', 'commentary', 'delete'].forEach(otherType => {
|
||||||
if (fileItem.hasAttribute('data-tagged-' + otherTagType)) {
|
if (otherType !== tagType && fileItem.hasAttribute('data-tagged-' + otherType)) {
|
||||||
untagFile(filePath, otherTagType);
|
untagFile(filePath, otherType);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Tag the file
|
// Tag the file
|
||||||
addTagToEpisode(filePath, tagType);
|
addTagToEpisode(filePath, tagType);
|
||||||
}
|
}
|
||||||
@ -284,8 +316,14 @@ function displayFiles(files) {
|
|||||||
|
|
||||||
// Get the file item and tag type
|
// Get the file item and tag type
|
||||||
const fileItemEl = this.closest('.file-item');
|
const fileItemEl = this.closest('.file-item');
|
||||||
const tagType = fileItemEl.hasAttribute('data-tagged-extra') ? 'extra' :
|
let tagType = null;
|
||||||
fileItemEl.hasAttribute('data-tagged-commentary') ? 'commentary' : null;
|
if (fileItemEl.hasAttribute('data-tagged-extra')) {
|
||||||
|
tagType = 'extra';
|
||||||
|
} else if (fileItemEl.hasAttribute('data-tagged-commentary')) {
|
||||||
|
tagType = 'commentary';
|
||||||
|
} else if (fileItemEl.hasAttribute('data-tagged-delete')) {
|
||||||
|
tagType = 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Tag type determined:', tagType);
|
console.log('Tag type determined:', tagType);
|
||||||
if (tagType) {
|
if (tagType) {
|
||||||
@ -295,6 +333,8 @@ function displayFiles(files) {
|
|||||||
// Remove the item from the file list after successful move
|
// Remove the item from the file list after successful move
|
||||||
fileItemEl.remove();
|
fileItemEl.remove();
|
||||||
updateTaggedCount();
|
updateTaggedCount();
|
||||||
|
updateEpisodeNumbers();
|
||||||
|
checkEpisodeCountMatch();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('No tag type found for file');
|
console.log('No tag type found for file');
|
||||||
@ -302,6 +342,81 @@ function displayFiles(files) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fileListEl.appendChild(fileItem);
|
fileListEl.appendChild(fileItem);
|
||||||
|
episodeNumber++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if episode count matches after displaying files
|
||||||
|
checkEpisodeCountMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag and drop handlers
|
||||||
|
let draggedItem = null;
|
||||||
|
|
||||||
|
function handleDragStart(e) {
|
||||||
|
draggedItem = this;
|
||||||
|
this.classList.add('dragging');
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData('text/plain', this.dataset.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd(e) {
|
||||||
|
this.classList.remove('dragging');
|
||||||
|
// Remove drag-over class from all items
|
||||||
|
document.querySelectorAll('.file-item').forEach(item => {
|
||||||
|
item.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
draggedItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
|
// Only show drag-over for non-folder items
|
||||||
|
if (!this.classList.contains('folder-item') && this !== draggedItem) {
|
||||||
|
this.classList.add('drag-over');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragLeave(e) {
|
||||||
|
this.classList.remove('drag-over');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.classList.remove('drag-over');
|
||||||
|
|
||||||
|
if (this === draggedItem || this.classList.contains('folder-item')) return;
|
||||||
|
|
||||||
|
// Get all media file items (not folders)
|
||||||
|
const fileItems = Array.from(fileListEl.querySelectorAll('.file-item:not(.folder-item)'));
|
||||||
|
const draggedIndex = fileItems.indexOf(draggedItem);
|
||||||
|
const dropIndex = fileItems.indexOf(this);
|
||||||
|
|
||||||
|
if (draggedIndex === -1 || dropIndex === -1) return;
|
||||||
|
|
||||||
|
// Move the dragged item in the DOM
|
||||||
|
if (draggedIndex < dropIndex) {
|
||||||
|
this.parentNode.insertBefore(draggedItem, this.nextSibling);
|
||||||
|
} else {
|
||||||
|
this.parentNode.insertBefore(draggedItem, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update episode numbers
|
||||||
|
updateEpisodeNumbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEpisodeNumbers() {
|
||||||
|
const fileItems = fileListEl.querySelectorAll('.file-item:not(.folder-item)');
|
||||||
|
let episodeNum = 1;
|
||||||
|
|
||||||
|
fileItems.forEach(item => {
|
||||||
|
const episodeEl = item.querySelector('.episode-number');
|
||||||
|
if (episodeEl) {
|
||||||
|
episodeEl.textContent = episodeNum;
|
||||||
|
episodeEl.dataset.episode = episodeNum;
|
||||||
|
episodeNum++;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,13 +499,22 @@ function addTagToEpisode(filePath, tagType) {
|
|||||||
// Add visual indication of tagging using data attribute for CSS styling
|
// Add visual indication of tagging using data attribute for CSS styling
|
||||||
const tagIcon = item.querySelector(`.${tagType}-tag`);
|
const tagIcon = item.querySelector(`.${tagType}-tag`);
|
||||||
if (tagIcon) {
|
if (tagIcon) {
|
||||||
// Make the icon fully saturated and highlight in bright yellow with very strong visual effect
|
// Set color based on tag type
|
||||||
|
let tagColor;
|
||||||
|
if (tagType === 'extra') {
|
||||||
|
tagColor = '#FFD700'; // Yellow
|
||||||
|
} else if (tagType === 'commentary') {
|
||||||
|
tagColor = '#17a2b8'; // Teal
|
||||||
|
} else if (tagType === 'delete') {
|
||||||
|
tagColor = '#dc3545'; // Red
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the icon fully saturated and highlight
|
||||||
tagIcon.style.opacity = '1';
|
tagIcon.style.opacity = '1';
|
||||||
tagIcon.style.filter = 'none';
|
tagIcon.style.filter = 'none';
|
||||||
tagIcon.style.color = tagType === 'extra' ? '#FFD700' : '#17a2b8'; // Brighter yellow or teal color
|
tagIcon.style.color = tagColor;
|
||||||
tagIcon.style.textShadow = '0 0 15px rgba(255, 215, 0, 1)'; // Very bright yellow glow (for extra tags)
|
tagIcon.style.textShadow = `0 0 15px ${tagColor}`;
|
||||||
tagIcon.style.transform = 'scale(1.3)'; // More pronounced enlargement
|
tagIcon.style.transform = 'scale(1.3)';
|
||||||
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
|
// Add a data attribute to track that this file is tagged
|
||||||
item.setAttribute('data-tagged-' + tagType, 'true');
|
item.setAttribute('data-tagged-' + tagType, 'true');
|
||||||
@ -448,7 +572,7 @@ function moveTaggedFile(filePath, tagType) {
|
|||||||
|
|
||||||
// Function to move all tagged files at once
|
// Function to move all tagged files at once
|
||||||
async function moveAllTaggedFiles() {
|
async function moveAllTaggedFiles() {
|
||||||
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary]');
|
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary], .file-item[data-tagged-delete]');
|
||||||
|
|
||||||
if (taggedItems.length === 0) {
|
if (taggedItems.length === 0) {
|
||||||
console.log('[RENDERER] No tagged files to move');
|
console.log('[RENDERER] No tagged files to move');
|
||||||
@ -467,7 +591,14 @@ async function moveAllTaggedFiles() {
|
|||||||
const results = [];
|
const results = [];
|
||||||
for (const item of taggedItems) {
|
for (const item of taggedItems) {
|
||||||
const filePath = item.querySelector('.file-name').dataset.filePath;
|
const filePath = item.querySelector('.file-name').dataset.filePath;
|
||||||
const tagType = item.hasAttribute('data-tagged-extra') ? 'extra' : 'commentary';
|
let tagType;
|
||||||
|
if (item.hasAttribute('data-tagged-extra')) {
|
||||||
|
tagType = 'extra';
|
||||||
|
} else if (item.hasAttribute('data-tagged-commentary')) {
|
||||||
|
tagType = 'commentary';
|
||||||
|
} else if (item.hasAttribute('data-tagged-delete')) {
|
||||||
|
tagType = 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
const result = await moveTaggedFile(filePath, tagType);
|
const result = await moveTaggedFile(filePath, tagType);
|
||||||
results.push(result);
|
results.push(result);
|
||||||
@ -486,6 +617,8 @@ async function moveAllTaggedFiles() {
|
|||||||
|
|
||||||
// Update the tagged count after all moves
|
// Update the tagged count after all moves
|
||||||
updateTaggedCount();
|
updateTaggedCount();
|
||||||
|
updateEpisodeNumbers();
|
||||||
|
checkEpisodeCountMatch();
|
||||||
|
|
||||||
// Summary of results
|
// Summary of results
|
||||||
const successful = results.filter(r => r.success).length;
|
const successful = results.filter(r => r.success).length;
|
||||||
@ -517,9 +650,20 @@ function displayCreatedFolder(folderName) {
|
|||||||
folderItem.className = 'folder-item';
|
folderItem.className = 'folder-item';
|
||||||
folderItem.setAttribute('data-folder-name', folderName);
|
folderItem.setAttribute('data-folder-name', folderName);
|
||||||
|
|
||||||
const displayName = folderName === 'extra' ? 'extras' : 'commentary';
|
let displayName, folderIcon, folderColor;
|
||||||
const folderIcon = folderName === 'extra' ? '📁' : '💬';
|
if (folderName === 'extra') {
|
||||||
const folderColor = folderName === 'extra' ? '#FFD700' : '#17a2b8';
|
displayName = 'extras';
|
||||||
|
folderIcon = '📁';
|
||||||
|
folderColor = '#FFD700';
|
||||||
|
} else if (folderName === 'commentary') {
|
||||||
|
displayName = 'commentary';
|
||||||
|
folderIcon = '💬';
|
||||||
|
folderColor = '#17a2b8';
|
||||||
|
} else if (folderName === 'delete') {
|
||||||
|
displayName = 'delete';
|
||||||
|
folderIcon = '🗑️';
|
||||||
|
folderColor = '#dc3545';
|
||||||
|
}
|
||||||
|
|
||||||
folderItem.innerHTML = `
|
folderItem.innerHTML = `
|
||||||
<div class="folder-icon" style="font-size: 24px;">${folderIcon}</div>
|
<div class="folder-icon" style="font-size: 24px;">${folderIcon}</div>
|
||||||
@ -533,7 +677,7 @@ function displayCreatedFolder(folderName) {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
background-color: #f8f9fa;
|
background-color: #16213e;
|
||||||
border: 2px solid ${folderColor};
|
border: 2px solid ${folderColor};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -579,7 +723,11 @@ function untagFile(filePath, tagType) {
|
|||||||
// Remove the data attribute
|
// Remove the data attribute
|
||||||
item.removeAttribute('data-tagged-' + tagType);
|
item.removeAttribute('data-tagged-' + tagType);
|
||||||
|
|
||||||
// Disable the play button when untagging
|
// Disable the play button when untagging (only if no other tags)
|
||||||
|
const hasOtherTags = item.hasAttribute('data-tagged-extra') ||
|
||||||
|
item.hasAttribute('data-tagged-commentary') ||
|
||||||
|
item.hasAttribute('data-tagged-delete');
|
||||||
|
if (!hasOtherTags) {
|
||||||
const playButton = item.querySelector('.play-button');
|
const playButton = item.querySelector('.play-button');
|
||||||
if (playButton) {
|
if (playButton) {
|
||||||
playButton.style.opacity = '0.3';
|
playButton.style.opacity = '0.3';
|
||||||
@ -589,6 +737,7 @@ function untagFile(filePath, tagType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the tagged count display
|
// Update the tagged count display
|
||||||
@ -601,7 +750,7 @@ function untagFile(filePath, tagType) {
|
|||||||
|
|
||||||
// Function to update the tagged items count display
|
// Function to update the tagged items count display
|
||||||
function updateTaggedCount() {
|
function updateTaggedCount() {
|
||||||
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary]');
|
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary], .file-item[data-tagged-delete]');
|
||||||
const count = taggedItems.length;
|
const count = taggedItems.length;
|
||||||
const taggedCircle = document.getElementById('tagged-circle');
|
const taggedCircle = document.getElementById('tagged-circle');
|
||||||
const taggedCount = document.getElementById('tagged-count');
|
const taggedCount = document.getElementById('tagged-count');
|
||||||
@ -624,6 +773,24 @@ function updateTaggedCount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to check if media file count matches selected season episode count
|
||||||
|
function checkEpisodeCountMatch() {
|
||||||
|
const fileList = document.getElementById('file-list');
|
||||||
|
// Count only actual media files (not folders)
|
||||||
|
const mediaFiles = document.querySelectorAll('.file-item:not(.folder-item)');
|
||||||
|
const mediaCount = mediaFiles.length;
|
||||||
|
|
||||||
|
if (selectedSeasonEpisodeCount > 0 && mediaCount === selectedSeasonEpisodeCount) {
|
||||||
|
// Perfect match - add green outline
|
||||||
|
fileList.style.border = '3px solid #28a745';
|
||||||
|
fileList.style.boxShadow = '0 0 15px rgba(40, 167, 69, 0.4)';
|
||||||
|
} else {
|
||||||
|
// No match or no season selected - remove green outline
|
||||||
|
fileList.style.border = '';
|
||||||
|
fileList.style.boxShadow = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize tagged count on page load
|
// Initialize tagged count on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
updateTaggedCount();
|
updateTaggedCount();
|
||||||
@ -769,26 +936,13 @@ function displaySeasons(seasons) {
|
|||||||
seasonsContainer.innerHTML = '';
|
seasonsContainer.innerHTML = '';
|
||||||
|
|
||||||
if (!seasons || seasons.length === 0) {
|
if (!seasons || seasons.length === 0) {
|
||||||
seasonsContainer.innerHTML = '<p>No seasons available.</p>';
|
seasonsContainer.innerHTML = '<p style="color: #888;">No seasons available.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a container for seasons
|
|
||||||
const seasonsList = document.createElement('div');
|
|
||||||
seasonsList.style.marginTop = '10px';
|
|
||||||
|
|
||||||
seasons.forEach(season => {
|
seasons.forEach(season => {
|
||||||
const seasonItem = document.createElement('div');
|
const seasonItem = document.createElement('div');
|
||||||
seasonItem.className = 'season-item';
|
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
|
// Format season display with episode count
|
||||||
let seasonDisplay = `Season ${season.number}`;
|
let seasonDisplay = `Season ${season.number}`;
|
||||||
@ -799,21 +953,25 @@ function displaySeasons(seasons) {
|
|||||||
const episodeCount = season.episodeCount || 0;
|
const episodeCount = season.episodeCount || 0;
|
||||||
|
|
||||||
seasonItem.innerHTML = `
|
seasonItem.innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<span>${seasonDisplay}</span>
|
<span>${seasonDisplay}</span>
|
||||||
<span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;">
|
<span style="background-color: #e94560; color: white; padding: 4px 8px; border-radius: 12px; font-size: 11px;">
|
||||||
${episodeCount} episodes
|
${episodeCount} eps
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add click event to fetch episodes
|
// Add click event to fetch episodes
|
||||||
seasonItem.addEventListener('click', () => {
|
seasonItem.addEventListener('click', () => {
|
||||||
|
// Remove selected class from all seasons
|
||||||
|
document.querySelectorAll('.season-item').forEach(item => item.classList.remove('selected'));
|
||||||
|
// Add selected class to clicked season
|
||||||
|
seasonItem.classList.add('selected');
|
||||||
fetchAndDisplayEpisodes(currentShow.id, season.number);
|
fetchAndDisplayEpisodes(currentShow.id, season.number);
|
||||||
});
|
});
|
||||||
|
|
||||||
seasonsList.appendChild(seasonItem);
|
seasonsContainer.appendChild(seasonItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
seasonsContainer.appendChild(seasonsList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and display episodes for a season
|
// Fetch and display episodes for a season
|
||||||
@ -849,70 +1007,50 @@ function displayEpisodes(episodes) {
|
|||||||
seasonsContainer.innerHTML = '';
|
seasonsContainer.innerHTML = '';
|
||||||
|
|
||||||
if (!episodes || episodes.length === 0) {
|
if (!episodes || episodes.length === 0) {
|
||||||
seasonsContainer.innerHTML = '<p>No episodes available.</p>';
|
seasonsContainer.innerHTML = '<p style="color: #888;">No episodes available.</p>';
|
||||||
|
selectedSeasonEpisodeCount = 0;
|
||||||
|
checkEpisodeCountMatch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a container for episodes table
|
// Store the episode count for this season
|
||||||
const episodesTable = document.createElement('div');
|
selectedSeasonEpisodeCount = episodes.length;
|
||||||
episodesTable.style.marginTop = '10px';
|
checkEpisodeCountMatch();
|
||||||
episodesTable.style.border = '1px solid #ddd';
|
|
||||||
episodesTable.style.borderRadius = '6px';
|
|
||||||
episodesTable.style.overflow = 'hidden';
|
|
||||||
|
|
||||||
// Create table header
|
// Create a back button to return to seasons
|
||||||
const tableHeader = document.createElement('div');
|
const backButton = document.createElement('div');
|
||||||
tableHeader.style.display = 'grid';
|
backButton.className = 'season-item';
|
||||||
tableHeader.style.gridTemplateColumns = '1fr 1fr';
|
backButton.innerHTML = '← Back to Seasons';
|
||||||
tableHeader.style.backgroundColor = '#f8f9fa';
|
backButton.addEventListener('click', () => {
|
||||||
tableHeader.style.padding = '10px';
|
selectedSeasonEpisodeCount = 0;
|
||||||
tableHeader.style.fontWeight = 'bold';
|
checkEpisodeCountMatch();
|
||||||
tableHeader.style.borderBottom = '1px solid #ddd';
|
displaySeasons(currentSeasons);
|
||||||
|
});
|
||||||
|
seasonsContainer.appendChild(backButton);
|
||||||
|
|
||||||
tableHeader.innerHTML = `
|
// Create episodes container
|
||||||
<div>Episode Name</div>
|
const episodesContainer = document.createElement('div');
|
||||||
<div>Runtime</div>
|
episodesContainer.id = 'episodes-container';
|
||||||
`;
|
|
||||||
|
|
||||||
episodesTable.appendChild(tableHeader);
|
|
||||||
|
|
||||||
episodes.forEach(episode => {
|
episodes.forEach(episode => {
|
||||||
const episodeRow = document.createElement('div');
|
const episodeRow = document.createElement('div');
|
||||||
episodeRow.className = 'episode-item';
|
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
|
// Format episode display
|
||||||
const episodeName = episode.name || 'Untitled';
|
const episodeName = episode.name || 'Untitled';
|
||||||
const episodeRuntime = episode.runtime ? `${episode.runtime} min` : 'N/A';
|
const episodeRuntime = episode.runtime ? `${episode.runtime}m` : '';
|
||||||
|
|
||||||
episodeRow.innerHTML = `
|
episodeRow.innerHTML = `
|
||||||
<div style="font-weight: 500;">Episode ${episode.number}: ${episodeName}</div>
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<div style="color: #666;">${episodeRuntime}</div>
|
<span><strong>E${episode.number}</strong> ${episodeName}</span>
|
||||||
|
${episodeRuntime ? `<span style="color: #888; font-size: 11px;">${episodeRuntime}</span>` : ''}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add click event (could show more details)
|
episodesContainer.appendChild(episodeRow);
|
||||||
episodeRow.addEventListener('click', () => {
|
|
||||||
alert(`Episode: ${episode.name || 'Untitled'}\nRuntime: ${episode.runtime || 'N/A'} min\nAired: ${episode.aired || 'N/A'}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
episodesTable.appendChild(episodeRow);
|
seasonsContainer.appendChild(episodesContainer);
|
||||||
});
|
|
||||||
|
|
||||||
seasonsContainer.appendChild(episodesTable);
|
|
||||||
|
|
||||||
// Also update the main file list to show episode matching information
|
// Also update the main file list to show episode matching information
|
||||||
updateFileListWithEpisodeInfo(episodes);
|
updateFileListWithEpisodeInfo(episodes);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user