MovieMapper/utils/renderer/TagManager.js
Jarian Cottingham be887c3855 Refactor renderer.js into modular class-based structure
- Split large renderer.js file into 8 separate class files:
  - AppState.js: Manages application state
  - FileListManager.js: Handles file list display and manipulation
  - TagManager.js: Manages file tagging functionality
  - EpisodeManager.js: Handles episode number editing and highlighting
  - SearchManager.js: Manages TVDB search and show selection
  - FileManager.js: Handles file operations
  - ModalManager.js: Manages video preview modal
  - ProgressManager.js: Manages progress display
  - UIManager.js: Main coordinator for UI functionality
- Follows SOLID principles and single responsibility
- Improves code maintainability and testability
- All syntax verified with node --check
2026-02-25 02:32:08 -06:00

257 lines
7.8 KiB
JavaScript

const { ipcRenderer } = require('electron');
/**
* TagManager - Manages file tagging functionality
*/
class TagManager {
constructor() {
this.tagColors = {
extra: '#FFD700', // Yellow
commentary: '#17a2b8', // Teal
delete: '#dc3545' // Red
};
}
/**
* Tag a file with a specific tag type
* @param {string} filePath - File path
* @param {string} tagType - Tag type (extra, commentary, delete)
* @param {Function} callback - Callback function
*/
tagFile(filePath, tagType, callback) {
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) {
this._applyTagVisuals(item, tagType);
item.setAttribute('data-tagged-' + tagType, 'true');
this._enablePlayButton(item);
}
});
// Update the tagged count display
if (callback && typeof callback === 'function') {
callback();
}
console.log(`File ${filePath} tagged as ${tagType}`);
}
/**
* Apply visual styling for a tag
* @param {HTMLElement} fileItem - File item element
* @param {string} tagType - Tag type
* @private
*/
_applyTagVisuals(fileItem, tagType) {
const tagIcon = fileItem.querySelector(`.${tagType}-tag`);
if (tagIcon) {
const tagColor = this.tagColors[tagType];
// Make the icon fully saturated and highlight
tagIcon.style.opacity = '1';
tagIcon.style.filter = 'none';
tagIcon.style.color = tagColor;
tagIcon.style.textShadow = `0 0 15px ${tagColor}`;
tagIcon.style.transform = 'scale(1.3)';
}
}
/**
* Enable play button for a file
* @param {HTMLElement} fileItem - File item element
* @private
*/
_enablePlayButton(fileItem) {
const playButton = fileItem.querySelector('.play-button');
if (playButton) {
playButton.style.opacity = '1';
playButton.style.cursor = 'pointer';
playButton.disabled = false;
playButton.style.pointerEvents = 'auto';
}
}
/**
* Untag a file
* @param {string} filePath - File path
* @param {string} tagType - Tag type
*/
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) {
this._removeTagVisuals(item, tagType);
item.removeAttribute('data-tagged-' + tagType);
this._checkAndDisablePlayButton(item);
}
});
}
/**
* Remove tag visuals
* @param {HTMLElement} fileItem - File item element
* @param {string} tagType - Tag type
* @private
*/
_removeTagVisuals(fileItem, tagType) {
const tagIcon = fileItem.querySelector(`.${tagType}-tag`);
if (tagIcon) {
// Reset to original appearance
tagIcon.style.opacity = '0.7';
tagIcon.style.filter = 'none';
tagIcon.style.color = '';
tagIcon.style.textShadow = 'none';
tagIcon.style.transform = 'scale(1)';
tagIcon.style.boxShadow = 'none';
}
}
/**
* Check and disable play button if no tags remain
* @param {HTMLElement} fileItem - File item element
* @private
*/
_checkAndDisablePlayButton(fileItem) {
const hasOtherTags = fileItem.hasAttribute('data-tagged-extra') ||
fileItem.hasAttribute('data-tagged-commentary') ||
fileItem.hasAttribute('data-tagged-delete');
if (!hasOtherTags) {
const playButton = fileItem.querySelector('.play-button');
if (playButton) {
playButton.style.opacity = '0.3';
playButton.style.cursor = 'default';
playButton.disabled = true;
playButton.style.pointerEvents = 'none';
}
}
}
/**
* Move a tagged file
* @param {string} filePath - File path
* @param {string} tagType - Tag type
* @returns {Promise<Object>} Move result
*/
async moveTaggedFile(filePath, tagType) {
console.log(`Moving file ${filePath} to ${tagType} folder`);
// Send request to main process to move the file
const result = await ipcRenderer.invoke('move-file-to-folder', {
filePath: filePath,
folderName: tagType
});
if (result.success) {
console.log(`File moved successfully to ${tagType} folder`);
return { success: true, filePath };
} else {
console.error(`Failed to move file: ${result.error}`);
return { success: false, error: result.error, filePath };
}
}
/**
* Move all tagged files
* @param {Function} onUpdateCount - Callback to update count
* @param {Function} onEpisodesUpdated - Callback to update episodes
* @param {Function} onMatchCheck - Callback to check episode match
* @returns {Promise<Object>} Results summary
*/
async moveAllTaggedFiles(onUpdateCount, onEpisodesUpdated, onMatchCheck) {
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary], .file-item[data-tagged-delete]');
if (taggedItems.length === 0) {
console.log('No tagged files to move');
return { success: true, successful: 0, failed: 0 };
}
console.log(`Moving ${taggedItems.length} tagged files`);
// Visual feedback - change circle color while processing
const taggedCircle = document.getElementById('tagged-circle');
if (taggedCircle) {
taggedCircle.style.backgroundColor = '#ffc107';
taggedCircle.style.pointerEvents = 'none';
}
const results = [];
for (const item of taggedItems) {
const filePath = item.querySelector('.file-name').dataset.filePath;
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 this.moveTaggedFile(filePath, tagType);
results.push(result);
if (result.success) {
// Remove the item from the file list after successful move
item.remove();
}
}
// Reset circle appearance
if (taggedCircle) {
taggedCircle.style.backgroundColor = '';
taggedCircle.style.pointerEvents = '';
}
// Update the tagged count after all moves
if (onUpdateCount) onUpdateCount();
if (onEpisodesUpdated) onEpisodesUpdated();
if (onMatchCheck) onMatchCheck();
// Summary of results
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
if (failed > 0) {
alert(`Moved ${successful} files. ${failed} files failed to move.`);
} else if (successful > 0) {
console.log(`Successfully moved all ${successful} files`);
}
return { success: true, successful, failed };
}
/**
* Get all tagged files
* @returns {Array} Array of tagged file objects
*/
getTaggedFiles() {
const taggedItems = document.querySelectorAll('.file-item[data-tagged-extra], .file-item[data-tagged-commentary], .file-item[data-tagged-delete]');
const taggedFiles = [];
taggedItems.forEach(item => {
const filePath = item.querySelector('.file-name').dataset.filePath;
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';
}
taggedFiles.push({ filePath, tagType });
});
return taggedFiles;
}
}
module.exports = TagManager;