refactor: split CSS and JS into modular files (SRP)
This commit is contained in:
439
src/server/web/static/js/index/scan-manager.js
Normal file
439
src/server/web/static/js/index/scan-manager.js
Normal file
@@ -0,0 +1,439 @@
|
||||
/**
|
||||
* AniWorld - Scan Manager Module
|
||||
*
|
||||
* Handles library scanning and progress overlay.
|
||||
*
|
||||
* Dependencies: constants.js, api-client.js, ui-utils.js
|
||||
*/
|
||||
|
||||
var AniWorld = window.AniWorld || {};
|
||||
|
||||
AniWorld.ScanManager = (function() {
|
||||
'use strict';
|
||||
|
||||
const API = AniWorld.Constants.API;
|
||||
const DEFAULTS = AniWorld.Constants.DEFAULTS;
|
||||
|
||||
// State
|
||||
let scanTotalItems = 0;
|
||||
let lastScanData = null;
|
||||
|
||||
/**
|
||||
* Initialize the scan manager
|
||||
*/
|
||||
function init() {
|
||||
bindEvents();
|
||||
// Check scan status on page load
|
||||
checkActiveScanStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind UI events
|
||||
*/
|
||||
function bindEvents() {
|
||||
const rescanBtn = document.getElementById('rescan-btn');
|
||||
if (rescanBtn) {
|
||||
rescanBtn.addEventListener('click', rescanSeries);
|
||||
}
|
||||
|
||||
// Click on rescan status indicator to reopen scan overlay
|
||||
const rescanStatus = document.getElementById('rescan-status');
|
||||
if (rescanStatus) {
|
||||
rescanStatus.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
console.log('Rescan status clicked');
|
||||
reopenScanOverlay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a rescan of the series directory
|
||||
*/
|
||||
async function rescanSeries() {
|
||||
try {
|
||||
// Show the overlay immediately before making the API call
|
||||
showScanProgressOverlay({
|
||||
directory: 'Starting scan...',
|
||||
total_items: 0
|
||||
});
|
||||
updateProcessStatus('rescan', true);
|
||||
|
||||
const response = await AniWorld.ApiClient.post(API.ANIME_RESCAN, {});
|
||||
|
||||
if (!response) {
|
||||
removeScanProgressOverlay();
|
||||
updateProcessStatus('rescan', false);
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// Debug logging
|
||||
console.log('Rescan response:', data);
|
||||
|
||||
// Note: The scan progress will be updated via WebSocket events
|
||||
// The overlay will be closed when scan_completed is received
|
||||
if (data.success !== true) {
|
||||
removeScanProgressOverlay();
|
||||
updateProcessStatus('rescan', false);
|
||||
AniWorld.UI.showToast('Rescan error: ' + data.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Rescan error:', error);
|
||||
removeScanProgressOverlay();
|
||||
updateProcessStatus('rescan', false);
|
||||
AniWorld.UI.showToast('Failed to start rescan', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the scan progress overlay
|
||||
* @param {Object} data - Scan started event data
|
||||
*/
|
||||
function showScanProgressOverlay(data) {
|
||||
// Remove existing overlay if present
|
||||
removeScanProgressOverlay();
|
||||
|
||||
// Store total items for progress calculation
|
||||
scanTotalItems = data?.total_items || 0;
|
||||
|
||||
// Store last scan data for reopening
|
||||
lastScanData = data;
|
||||
|
||||
// Create overlay element
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'scan-progress-overlay';
|
||||
overlay.className = 'scan-progress-overlay';
|
||||
|
||||
const totalDisplay = scanTotalItems > 0 ? scanTotalItems : '...';
|
||||
|
||||
overlay.innerHTML =
|
||||
'<div class="scan-progress-container">' +
|
||||
'<div class="scan-progress-header">' +
|
||||
'<h3>' +
|
||||
'<span class="scan-progress-spinner"></span>' +
|
||||
'<i class="fas fa-check-circle scan-completed-icon"></i>' +
|
||||
'<span class="scan-title-text">Scanning Library</span>' +
|
||||
'</h3>' +
|
||||
'</div>' +
|
||||
'<div class="scan-progress-bar-container">' +
|
||||
'<div class="scan-progress-bar" id="scan-progress-bar" style="width: 0%"></div>' +
|
||||
'</div>' +
|
||||
'<div class="scan-progress-text" id="scan-progress-text">' +
|
||||
'<span id="scan-current-count">0</span> / <span id="scan-total-count">' + totalDisplay + '</span> directories' +
|
||||
'</div>' +
|
||||
'<div class="scan-progress-stats">' +
|
||||
'<div class="scan-stat">' +
|
||||
'<span class="scan-stat-value" id="scan-directories-count">0</span>' +
|
||||
'<span class="scan-stat-label">Scanned</span>' +
|
||||
'</div>' +
|
||||
'<div class="scan-stat">' +
|
||||
'<span class="scan-stat-value" id="scan-files-count">0</span>' +
|
||||
'<span class="scan-stat-label">Series Found</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="scan-current-directory" id="scan-current-directory">' +
|
||||
'<span class="scan-current-directory-label">Current:</span>' +
|
||||
'<span id="scan-current-path">' + AniWorld.UI.escapeHtml(data?.directory || 'Initializing...') + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="scan-elapsed-time hidden" id="scan-elapsed-time">' +
|
||||
'<i class="fas fa-clock"></i>' +
|
||||
'<span id="scan-elapsed-value">0.0s</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Add click-outside-to-close handler
|
||||
overlay.addEventListener('click', function(e) {
|
||||
// Only close if clicking the overlay background, not the container
|
||||
if (e.target === overlay) {
|
||||
removeScanProgressOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger animation by adding visible class after a brief delay
|
||||
requestAnimationFrame(function() {
|
||||
overlay.classList.add('visible');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the scan progress overlay
|
||||
* @param {Object} data - Scan progress event data
|
||||
*/
|
||||
function updateScanProgressOverlay(data) {
|
||||
const overlay = document.getElementById('scan-progress-overlay');
|
||||
if (!overlay) return;
|
||||
|
||||
// Update total items if provided
|
||||
if (data.total_items && data.total_items > 0) {
|
||||
scanTotalItems = data.total_items;
|
||||
const totalCount = document.getElementById('scan-total-count');
|
||||
if (totalCount) {
|
||||
totalCount.textContent = scanTotalItems;
|
||||
}
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
const progressBar = document.getElementById('scan-progress-bar');
|
||||
if (progressBar && scanTotalItems > 0 && data.directories_scanned !== undefined) {
|
||||
const percentage = Math.min(100, (data.directories_scanned / scanTotalItems) * 100);
|
||||
progressBar.style.width = percentage + '%';
|
||||
}
|
||||
|
||||
// Update current/total count display
|
||||
const currentCount = document.getElementById('scan-current-count');
|
||||
if (currentCount && data.directories_scanned !== undefined) {
|
||||
currentCount.textContent = data.directories_scanned;
|
||||
}
|
||||
|
||||
// Update directories count
|
||||
const dirCount = document.getElementById('scan-directories-count');
|
||||
if (dirCount && data.directories_scanned !== undefined) {
|
||||
dirCount.textContent = data.directories_scanned;
|
||||
}
|
||||
|
||||
// Update files/series count
|
||||
const filesCount = document.getElementById('scan-files-count');
|
||||
if (filesCount && data.files_found !== undefined) {
|
||||
filesCount.textContent = data.files_found;
|
||||
}
|
||||
|
||||
// Update current directory (truncate if too long)
|
||||
const currentPath = document.getElementById('scan-current-path');
|
||||
if (currentPath && data.current_directory) {
|
||||
const maxLength = 50;
|
||||
let displayPath = data.current_directory;
|
||||
if (displayPath.length > maxLength) {
|
||||
displayPath = '...' + displayPath.slice(-maxLength + 3);
|
||||
}
|
||||
currentPath.textContent = displayPath;
|
||||
currentPath.title = data.current_directory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the scan progress overlay with completion summary
|
||||
* @param {Object} data - Scan completed event data
|
||||
*/
|
||||
function hideScanProgressOverlay(data) {
|
||||
const overlay = document.getElementById('scan-progress-overlay');
|
||||
if (!overlay) return;
|
||||
|
||||
const container = overlay.querySelector('.scan-progress-container');
|
||||
if (container) {
|
||||
container.classList.add('completed');
|
||||
}
|
||||
|
||||
// Update title
|
||||
const titleText = overlay.querySelector('.scan-title-text');
|
||||
if (titleText) {
|
||||
titleText.textContent = 'Scan Complete';
|
||||
}
|
||||
|
||||
// Complete the progress bar
|
||||
const progressBar = document.getElementById('scan-progress-bar');
|
||||
if (progressBar) {
|
||||
progressBar.style.width = '100%';
|
||||
}
|
||||
|
||||
// Update final stats
|
||||
if (data) {
|
||||
const dirCount = document.getElementById('scan-directories-count');
|
||||
if (dirCount && data.total_directories !== undefined) {
|
||||
dirCount.textContent = data.total_directories;
|
||||
}
|
||||
|
||||
const filesCount = document.getElementById('scan-files-count');
|
||||
if (filesCount && data.total_files !== undefined) {
|
||||
filesCount.textContent = data.total_files;
|
||||
}
|
||||
|
||||
// Update progress text to show final count
|
||||
const currentCount = document.getElementById('scan-current-count');
|
||||
const totalCount = document.getElementById('scan-total-count');
|
||||
if (currentCount && data.total_directories !== undefined) {
|
||||
currentCount.textContent = data.total_directories;
|
||||
}
|
||||
if (totalCount && data.total_directories !== undefined) {
|
||||
totalCount.textContent = data.total_directories;
|
||||
}
|
||||
|
||||
// Show elapsed time
|
||||
const elapsedTimeEl = document.getElementById('scan-elapsed-time');
|
||||
const elapsedValueEl = document.getElementById('scan-elapsed-value');
|
||||
if (elapsedTimeEl && elapsedValueEl && data.elapsed_seconds !== undefined) {
|
||||
elapsedValueEl.textContent = data.elapsed_seconds.toFixed(1) + 's';
|
||||
elapsedTimeEl.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Update current directory to show completion message
|
||||
const currentPath = document.getElementById('scan-current-path');
|
||||
if (currentPath) {
|
||||
currentPath.textContent = 'Scan finished successfully';
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-dismiss after 3 seconds
|
||||
setTimeout(function() {
|
||||
removeScanProgressOverlay();
|
||||
}, DEFAULTS.SCAN_AUTO_DISMISS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the scan progress overlay from the DOM
|
||||
*/
|
||||
function removeScanProgressOverlay() {
|
||||
const overlay = document.getElementById('scan-progress-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.remove('visible');
|
||||
// Wait for fade out animation before removing
|
||||
setTimeout(function() {
|
||||
if (overlay.parentElement) {
|
||||
overlay.remove();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen the scan progress overlay if a scan is in progress
|
||||
*/
|
||||
async function reopenScanOverlay() {
|
||||
// Check if overlay already exists
|
||||
const existingOverlay = document.getElementById('scan-progress-overlay');
|
||||
if (existingOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if scan is running via API
|
||||
try {
|
||||
const response = await AniWorld.ApiClient.get(API.ANIME_SCAN_STATUS);
|
||||
if (!response || !response.ok) {
|
||||
console.log('Could not fetch scan status');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Scan status for reopen:', data);
|
||||
|
||||
if (data.is_scanning) {
|
||||
// A scan is in progress, show the overlay
|
||||
showScanProgressOverlay({
|
||||
directory: data.directory,
|
||||
total_items: data.total_items
|
||||
});
|
||||
|
||||
// Update with current progress
|
||||
updateScanProgressOverlay({
|
||||
directories_scanned: data.directories_scanned,
|
||||
files_found: data.directories_scanned,
|
||||
current_directory: data.current_directory,
|
||||
total_items: data.total_items
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking scan status for reopen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a scan is currently in progress
|
||||
*/
|
||||
async function checkActiveScanStatus() {
|
||||
try {
|
||||
const response = await AniWorld.ApiClient.get(API.ANIME_SCAN_STATUS);
|
||||
if (!response || !response.ok) {
|
||||
console.log('Could not fetch scan status, response:', response?.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Scan status check result:', data);
|
||||
|
||||
if (data.is_scanning) {
|
||||
console.log('Scan is active, updating UI indicators');
|
||||
|
||||
// Update the process status indicator
|
||||
updateProcessStatus('rescan', true);
|
||||
|
||||
// Show the overlay
|
||||
showScanProgressOverlay({
|
||||
directory: data.directory,
|
||||
total_items: data.total_items
|
||||
});
|
||||
|
||||
// Update with current progress
|
||||
updateScanProgressOverlay({
|
||||
directories_scanned: data.directories_scanned,
|
||||
files_found: data.directories_scanned,
|
||||
current_directory: data.current_directory,
|
||||
total_items: data.total_items
|
||||
});
|
||||
} else {
|
||||
console.log('No active scan detected');
|
||||
updateProcessStatus('rescan', false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking scan status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update process status indicator
|
||||
* @param {string} processName - Process name (e.g., 'rescan', 'download')
|
||||
* @param {boolean} isRunning - Whether the process is running
|
||||
* @param {boolean} hasError - Whether there's an error
|
||||
*/
|
||||
function updateProcessStatus(processName, isRunning, hasError) {
|
||||
hasError = hasError || false;
|
||||
const statusElement = document.getElementById(processName + '-status');
|
||||
if (!statusElement) {
|
||||
console.warn('Process status element not found: ' + processName + '-status');
|
||||
return;
|
||||
}
|
||||
|
||||
const statusDot = statusElement.querySelector('.status-dot');
|
||||
if (!statusDot) {
|
||||
console.warn('Status dot not found in ' + processName + '-status element');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all status classes
|
||||
statusDot.classList.remove('idle', 'running', 'error');
|
||||
statusElement.classList.remove('running', 'error', 'idle');
|
||||
|
||||
// Capitalize process name for display
|
||||
const displayName = processName.charAt(0).toUpperCase() + processName.slice(1);
|
||||
|
||||
if (hasError) {
|
||||
statusDot.classList.add('error');
|
||||
statusElement.classList.add('error');
|
||||
statusElement.title = displayName + ' error - click for details';
|
||||
} else if (isRunning) {
|
||||
statusDot.classList.add('running');
|
||||
statusElement.classList.add('running');
|
||||
statusElement.title = displayName + ' is running...';
|
||||
} else {
|
||||
statusDot.classList.add('idle');
|
||||
statusElement.classList.add('idle');
|
||||
statusElement.title = displayName + ' is idle';
|
||||
}
|
||||
|
||||
console.log('Process status updated: ' + processName + ' = ' + (isRunning ? 'running' : (hasError ? 'error' : 'idle')));
|
||||
}
|
||||
|
||||
// Public API
|
||||
return {
|
||||
init: init,
|
||||
rescanSeries: rescanSeries,
|
||||
showScanProgressOverlay: showScanProgressOverlay,
|
||||
updateScanProgressOverlay: updateScanProgressOverlay,
|
||||
hideScanProgressOverlay: hideScanProgressOverlay,
|
||||
removeScanProgressOverlay: removeScanProgressOverlay,
|
||||
reopenScanOverlay: reopenScanOverlay,
|
||||
checkActiveScanStatus: checkActiveScanStatus,
|
||||
updateProcessStatus: updateProcessStatus
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user