refactor: split CSS and JS into modular files (SRP)
This commit is contained in:
421
src/server/web/static/js/index/socket-handler.js
Normal file
421
src/server/web/static/js/index/socket-handler.js
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* AniWorld - Socket Handler Module for Index Page
|
||||
*
|
||||
* Handles WebSocket events specific to the index page.
|
||||
*
|
||||
* Dependencies: constants.js, websocket-client.js, ui-utils.js, scan-manager.js, series-manager.js
|
||||
*/
|
||||
|
||||
var AniWorld = window.AniWorld || {};
|
||||
|
||||
AniWorld.IndexSocketHandler = (function() {
|
||||
'use strict';
|
||||
|
||||
const WS_EVENTS = AniWorld.Constants.WS_EVENTS;
|
||||
|
||||
// State
|
||||
let isDownloading = false;
|
||||
let isPaused = false;
|
||||
let localization = null;
|
||||
|
||||
/**
|
||||
* Initialize socket handler
|
||||
* @param {Object} localizationObj - Localization object
|
||||
*/
|
||||
function init(localizationObj) {
|
||||
localization = localizationObj;
|
||||
setupSocketHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localized text
|
||||
*/
|
||||
function getText(key) {
|
||||
if (localization && localization.getText) {
|
||||
return localization.getText(key);
|
||||
}
|
||||
// Fallback text
|
||||
const fallbacks = {
|
||||
'connected-server': 'Connected to server',
|
||||
'disconnected-server': 'Disconnected from server',
|
||||
'download-completed': 'Download completed',
|
||||
'download-failed': 'Download failed',
|
||||
'paused': 'Paused',
|
||||
'downloading': 'Downloading...',
|
||||
'connected': 'Connected',
|
||||
'disconnected': 'Disconnected'
|
||||
};
|
||||
return fallbacks[key] || key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up WebSocket event handlers
|
||||
*/
|
||||
function setupSocketHandlers() {
|
||||
const socket = AniWorld.WebSocketClient.getSocket();
|
||||
if (!socket) {
|
||||
console.warn('Socket not available for handler setup');
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection events
|
||||
socket.on('connect', function() {
|
||||
AniWorld.UI.showToast(getText('connected-server'), 'success');
|
||||
updateConnectionStatus(true);
|
||||
AniWorld.ScanManager.checkActiveScanStatus();
|
||||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
AniWorld.UI.showToast(getText('disconnected-server'), 'warning');
|
||||
updateConnectionStatus(false);
|
||||
});
|
||||
|
||||
// Scan events
|
||||
socket.on(WS_EVENTS.SCAN_STARTED, function(data) {
|
||||
console.log('Scan started:', data);
|
||||
AniWorld.ScanManager.showScanProgressOverlay(data);
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', true);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.SCAN_PROGRESS, function(data) {
|
||||
console.log('Scan progress:', data);
|
||||
AniWorld.ScanManager.updateScanProgressOverlay(data);
|
||||
});
|
||||
|
||||
// Handle both legacy and new scan complete events
|
||||
const handleScanComplete = function(data) {
|
||||
console.log('Scan completed:', data);
|
||||
AniWorld.ScanManager.hideScanProgressOverlay(data);
|
||||
AniWorld.UI.showToast('Scan completed successfully', 'success');
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', false);
|
||||
AniWorld.SeriesManager.loadSeries();
|
||||
};
|
||||
socket.on(WS_EVENTS.SCAN_COMPLETED, handleScanComplete);
|
||||
socket.on(WS_EVENTS.SCAN_COMPLETE, handleScanComplete);
|
||||
|
||||
// Handle scan errors
|
||||
const handleScanError = function(data) {
|
||||
AniWorld.ConfigManager.hideStatus();
|
||||
AniWorld.UI.showToast('Scan error: ' + (data.message || data.error), 'error');
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', false, true);
|
||||
};
|
||||
socket.on(WS_EVENTS.SCAN_ERROR, handleScanError);
|
||||
socket.on(WS_EVENTS.SCAN_FAILED, handleScanError);
|
||||
|
||||
// Scheduled scan events
|
||||
socket.on(WS_EVENTS.SCHEDULED_RESCAN_STARTED, function() {
|
||||
AniWorld.UI.showToast('Scheduled rescan started', 'info');
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', true);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.SCHEDULED_RESCAN_COMPLETED, function(data) {
|
||||
AniWorld.UI.showToast('Scheduled rescan completed successfully', 'success');
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', false);
|
||||
AniWorld.SeriesManager.loadSeries();
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.SCHEDULED_RESCAN_ERROR, function(data) {
|
||||
AniWorld.UI.showToast('Scheduled rescan error: ' + data.error, 'error');
|
||||
AniWorld.ScanManager.updateProcessStatus('rescan', false, true);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.SCHEDULED_RESCAN_SKIPPED, function(data) {
|
||||
AniWorld.UI.showToast('Scheduled rescan skipped: ' + data.reason, 'warning');
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.AUTO_DOWNLOAD_STARTED, function(data) {
|
||||
AniWorld.UI.showToast('Auto-download started after scheduled rescan', 'info');
|
||||
AniWorld.ScanManager.updateProcessStatus('download', true);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.AUTO_DOWNLOAD_ERROR, function(data) {
|
||||
AniWorld.UI.showToast('Auto-download error: ' + data.error, 'error');
|
||||
AniWorld.ScanManager.updateProcessStatus('download', false, true);
|
||||
});
|
||||
|
||||
// Download events
|
||||
socket.on(WS_EVENTS.DOWNLOAD_STARTED, function(data) {
|
||||
isDownloading = true;
|
||||
isPaused = false;
|
||||
AniWorld.ScanManager.updateProcessStatus('download', true);
|
||||
showDownloadQueue(data);
|
||||
showStatus('Starting download of ' + data.total_series + ' series...', true, true);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_PROGRESS, function(data) {
|
||||
let status = '';
|
||||
let percent = 0;
|
||||
|
||||
if (data.progress !== undefined) {
|
||||
percent = data.progress;
|
||||
status = 'Downloading: ' + percent.toFixed(1) + '%';
|
||||
|
||||
if (data.speed_mbps && data.speed_mbps > 0) {
|
||||
status += ' (' + data.speed_mbps.toFixed(1) + ' Mbps)';
|
||||
}
|
||||
|
||||
if (data.eta_seconds && data.eta_seconds > 0) {
|
||||
const eta = AniWorld.UI.formatETA(data.eta_seconds);
|
||||
status += ' - ETA: ' + eta;
|
||||
}
|
||||
} else if (data.total_bytes) {
|
||||
percent = ((data.downloaded_bytes || 0) / data.total_bytes * 100);
|
||||
status = 'Downloading: ' + percent.toFixed(1) + '%';
|
||||
} else if (data.downloaded_mb !== undefined) {
|
||||
status = 'Downloaded: ' + data.downloaded_mb.toFixed(1) + ' MB';
|
||||
} else {
|
||||
status = 'Downloading: ' + (data.percent || '0%');
|
||||
}
|
||||
|
||||
if (percent > 0) {
|
||||
updateProgress(percent, status);
|
||||
} else {
|
||||
updateStatus(status);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_COMPLETED, function(data) {
|
||||
isDownloading = false;
|
||||
isPaused = false;
|
||||
hideDownloadQueue();
|
||||
AniWorld.ConfigManager.hideStatus();
|
||||
AniWorld.UI.showToast(getText('download-completed'), 'success');
|
||||
AniWorld.SeriesManager.loadSeries();
|
||||
AniWorld.SelectionManager.clearSelection();
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_ERROR, function(data) {
|
||||
isDownloading = false;
|
||||
isPaused = false;
|
||||
hideDownloadQueue();
|
||||
AniWorld.ConfigManager.hideStatus();
|
||||
AniWorld.UI.showToast(getText('download-failed') + ': ' + data.message, 'error');
|
||||
});
|
||||
|
||||
// Download queue events
|
||||
socket.on(WS_EVENTS.DOWNLOAD_QUEUE_COMPLETED, function() {
|
||||
AniWorld.ScanManager.updateProcessStatus('download', false);
|
||||
AniWorld.UI.showToast('All downloads completed!', 'success');
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_STOP_REQUESTED, function() {
|
||||
AniWorld.UI.showToast('Stopping downloads...', 'info');
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_STOPPED, function() {
|
||||
AniWorld.ScanManager.updateProcessStatus('download', false);
|
||||
AniWorld.UI.showToast('Downloads stopped', 'success');
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_QUEUE_UPDATE, function(data) {
|
||||
updateDownloadQueue(data);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_EPISODE_UPDATE, function(data) {
|
||||
updateCurrentEpisode(data);
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_SERIES_COMPLETED, function(data) {
|
||||
updateDownloadProgress(data);
|
||||
});
|
||||
|
||||
// Download control events
|
||||
socket.on(WS_EVENTS.DOWNLOAD_PAUSED, function() {
|
||||
isPaused = true;
|
||||
updateStatus(getText('paused'));
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_RESUMED, function() {
|
||||
isPaused = false;
|
||||
updateStatus(getText('downloading'));
|
||||
});
|
||||
|
||||
socket.on(WS_EVENTS.DOWNLOAD_CANCELLED, function() {
|
||||
isDownloading = false;
|
||||
isPaused = false;
|
||||
hideDownloadQueue();
|
||||
AniWorld.ConfigManager.hideStatus();
|
||||
AniWorld.UI.showToast('Download cancelled', 'warning');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update connection status display
|
||||
*/
|
||||
function updateConnectionStatus(connected) {
|
||||
const indicator = document.getElementById('connection-status-display');
|
||||
if (indicator) {
|
||||
const statusIndicator = indicator.querySelector('.status-indicator');
|
||||
const statusText = indicator.querySelector('.status-text');
|
||||
|
||||
if (connected) {
|
||||
statusIndicator.classList.add('connected');
|
||||
statusText.textContent = getText('connected');
|
||||
} else {
|
||||
statusIndicator.classList.remove('connected');
|
||||
statusText.textContent = getText('disconnected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show status panel
|
||||
*/
|
||||
function showStatus(message, showProgress, showControls) {
|
||||
showProgress = showProgress || false;
|
||||
showControls = showControls || false;
|
||||
|
||||
const panel = document.getElementById('status-panel');
|
||||
const messageEl = document.getElementById('status-message');
|
||||
const progressContainer = document.getElementById('progress-container');
|
||||
const controlsContainer = document.getElementById('download-controls');
|
||||
|
||||
messageEl.textContent = message;
|
||||
progressContainer.classList.toggle('hidden', !showProgress);
|
||||
controlsContainer.classList.toggle('hidden', !showControls);
|
||||
|
||||
if (showProgress) {
|
||||
updateProgress(0);
|
||||
}
|
||||
|
||||
panel.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status message
|
||||
*/
|
||||
function updateStatus(message) {
|
||||
document.getElementById('status-message').textContent = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress bar
|
||||
*/
|
||||
function updateProgress(percent, message) {
|
||||
const fill = document.getElementById('progress-fill');
|
||||
const text = document.getElementById('progress-text');
|
||||
|
||||
fill.style.width = percent + '%';
|
||||
text.textContent = message || percent + '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show download queue
|
||||
*/
|
||||
function showDownloadQueue(data) {
|
||||
const queueSection = document.getElementById('download-queue-section');
|
||||
const queueProgress = document.getElementById('queue-progress');
|
||||
|
||||
queueProgress.textContent = '0/' + data.total_series + ' series';
|
||||
updateDownloadQueue({
|
||||
queue: data.queue || [],
|
||||
current_downloading: null,
|
||||
stats: {
|
||||
completed_series: 0,
|
||||
total_series: data.total_series
|
||||
}
|
||||
});
|
||||
|
||||
queueSection.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide download queue
|
||||
*/
|
||||
function hideDownloadQueue() {
|
||||
const queueSection = document.getElementById('download-queue-section');
|
||||
const currentDownload = document.getElementById('current-download');
|
||||
|
||||
queueSection.classList.add('hidden');
|
||||
currentDownload.classList.add('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update download queue display
|
||||
*/
|
||||
function updateDownloadQueue(data) {
|
||||
const queueList = document.getElementById('queue-list');
|
||||
const currentDownload = document.getElementById('current-download');
|
||||
const queueProgress = document.getElementById('queue-progress');
|
||||
|
||||
// Update overall progress
|
||||
if (data.stats) {
|
||||
queueProgress.textContent = data.stats.completed_series + '/' + data.stats.total_series + ' series';
|
||||
}
|
||||
|
||||
// Update current downloading
|
||||
if (data.current_downloading) {
|
||||
currentDownload.classList.remove('hidden');
|
||||
document.getElementById('current-serie-name').textContent = AniWorld.UI.getDisplayName(data.current_downloading);
|
||||
document.getElementById('current-episode').textContent = data.current_downloading.missing_episodes + ' episodes remaining';
|
||||
} else {
|
||||
currentDownload.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Update queue list
|
||||
if (data.queue && data.queue.length > 0) {
|
||||
queueList.innerHTML = data.queue.map(function(serie, index) {
|
||||
return '<div class="queue-item">' +
|
||||
'<div class="queue-item-index">' + (index + 1) + '</div>' +
|
||||
'<div class="queue-item-name">' + AniWorld.UI.escapeHtml(AniWorld.UI.getDisplayName(serie)) + '</div>' +
|
||||
'<div class="queue-item-status">Waiting</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
} else {
|
||||
queueList.innerHTML = '<div class="queue-empty">No series in queue</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current episode display
|
||||
*/
|
||||
function updateCurrentEpisode(data) {
|
||||
const currentEpisode = document.getElementById('current-episode');
|
||||
const progressFill = document.getElementById('current-progress-fill');
|
||||
const progressText = document.getElementById('current-progress-text');
|
||||
|
||||
if (currentEpisode && data.episode) {
|
||||
currentEpisode.textContent = data.episode + ' (' + data.episode_progress + ')';
|
||||
}
|
||||
|
||||
if (data.overall_progress && progressFill && progressText) {
|
||||
const parts = data.overall_progress.split('/');
|
||||
const current = parseInt(parts[0]);
|
||||
const total = parseInt(parts[1]);
|
||||
const percent = total > 0 ? (current / total * 100).toFixed(1) : 0;
|
||||
|
||||
progressFill.style.width = percent + '%';
|
||||
progressText.textContent = percent + '%';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update download progress display
|
||||
*/
|
||||
function updateDownloadProgress(data) {
|
||||
const queueProgress = document.getElementById('queue-progress');
|
||||
|
||||
if (queueProgress && data.completed_series && data.total_series) {
|
||||
queueProgress.textContent = data.completed_series + '/' + data.total_series + ' series';
|
||||
}
|
||||
|
||||
AniWorld.UI.showToast('Completed: ' + data.serie, 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get download state
|
||||
*/
|
||||
function getDownloadState() {
|
||||
return {
|
||||
isDownloading: isDownloading,
|
||||
isPaused: isPaused
|
||||
};
|
||||
}
|
||||
|
||||
// Public API
|
||||
return {
|
||||
init: init,
|
||||
updateConnectionStatus: updateConnectionStatus,
|
||||
getDownloadState: getDownloadState
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user