diff --git a/static/script.js b/static/script.js index ff8d6af..d1f9f62 100644 --- a/static/script.js +++ b/static/script.js @@ -9,41 +9,59 @@ document.addEventListener('DOMContentLoaded', () => { ytFormatRadios: 'input[name="yt_format"]', mp3QualitySection: '#mp3-quality-section', mp4QualitySection: '#mp4-quality-section', - codecOptionsSection: '#codec-options-section', // Für Video Codec + codecOptionsSection: '#codec-options-section', progressBar: '#progress-bar', statusMessage: '#status-message', logContent: '#log-content', logOutput: '#log-output', resultUrlArea: '#result-url-area', resultUrlLink: '#result-url', - copyResultUrlLink: '#copy-result-url', // Nicht mehr im HTML, aber lassen wir es hier + copyResultUrlLink: '#copy-result-url', errorMessage: '#error-message', historyTableBody: '#history-table tbody', clearHistoryButton: '#clear-history-button', contextMenu: '#context-menu', - queueInfo: '#queue-info', // Behalten wir für evtl. spätere Nutzung + queueInfo: '#queue-info', statsTotalJobs: '#stats-total-jobs', statsAvgDuration: '#stats-avg-duration', statsTotalSize: '#stats-total-size', urlHelpText: '#urlHelp', + // Selektoren für das Overlay + processingOverlay: '#processing-overlay', + overlayMessage: '#overlay-message', + // NEU: Selektoren für Status im Overlay + overlayStatusText: '#overlay-status-text', + overlayProgressBar: '#overlay-progress-bar', }; - const dom = {}; // Objekt für DOM-Elemente + const dom = {}; let pollingInterval = null; - let currentJobId = null; // Aktuelle Job-ID speichern - let isPolling = false; // Separater State für aktives Polling + let currentJobId = null; + let isPolling = false; const historyEnabled = !!document.querySelector(selectors.clearHistoryButton); const videoPlatforms = ['YouTube', 'TikTok', 'Instagram', 'Twitter']; + const memeMessages = [ + "Hacking the mainframe...", + "Route Gibson durch die Firewall...", + "Einen Moment, ich Binge gerade das Internet durch...", + "Lade 1.21 Gigawatt herunter...", + "Komprimiere die Daten... mit purer Willenskraft.", + "Die Bits und Bytes tanzen Cha-Cha-Cha.", + "Frage die NSA nach dem schnellsten Weg...", + "Polishing the pixels...", + "Die Leitung glüht, alles nach Plan!", + "Füttere den Hamster im Serverraum...", + "Kalibriere den Fluxkompensator...", + "Optimiere den Warp-Antrieb...", + ]; + // --- Initialisierung --- - // (Unverändert) function init() { for (const key in selectors) { dom[key] = document.querySelector(selectors[key]); - if (!dom[key] && ['form', 'submitButton', 'statusMessage', 'progressBar', 'logContent', 'errorMessage', 'queueInfo', 'statsTotalJobs', 'statsAvgDuration', 'statsTotalSize', 'codecOptionsSection', 'urlHelpText'].includes(key)) { - console.warn(`Optionales DOM-Element nicht gefunden: ${selectors[key]}`); - } else if (!dom[key] && !['copyResultUrlLink', 'clearHistoryButton', 'historyTableBody', 'contextMenu'].includes(key)) { - console.error(`Kritisches DOM-Element nicht gefunden: ${selectors[key]}`); + if (!dom[key] && !['copyResultUrlLink', 'clearHistoryButton', 'historyTableBody', 'contextMenu'].includes(key)) { + console.warn(`DOM-Element nicht gefunden: ${selectors[key]}`); } } dom.platformRadios = document.querySelectorAll(selectors.platformRadios); @@ -60,29 +78,15 @@ document.addEventListener('DOMContentLoaded', () => { } resetUIState(); fetchStats(); - console.log("Uploader UI initialisiert."); - console.log("History aktiviert (Frontend):", historyEnabled); } // --- Event Listener Setup --- - // (Unverändert) function setupEventListeners() { if (dom.form) dom.form.addEventListener('submit', handleFormSubmit); dom.platformRadios.forEach(radio => radio.addEventListener('change', updateDynamicOptionsVisibility)); dom.ytFormatRadios.forEach(radio => radio.addEventListener('change', updateYoutubeQualityVisibility)); if (dom.clearHistoryButton) dom.clearHistoryButton.addEventListener('click', handleClearHistory); - - if (dom.copyResultUrlLink) { - dom.copyResultUrlLink.addEventListener('click', (e) => { - e.preventDefault(); - if (dom.resultUrlLink && dom.resultUrlLink.href) { - copyToClipboard(dom.resultUrlLink.href); - appendLog("Ergebnis-URL kopiert.", "info"); - } - }); - } - document.addEventListener('click', hideContextMenu); if (dom.contextMenu) dom.contextMenu.addEventListener('click', handleContextMenuClick); if (dom.historyTableBody && historyEnabled) { @@ -94,80 +98,83 @@ document.addEventListener('DOMContentLoaded', () => { } // --- UI Update Funktionen --- - // (Unverändert) function resetUIState() { if (dom.submitButton) { dom.submitButton.disabled = false; dom.submitButton.innerHTML = ' Download starten'; } - if (dom.statusMessage) dom.statusMessage.textContent = 'Bereit.'; if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); if (dom.resultUrlArea) dom.resultUrlArea.classList.add('d-none'); - updateProgressBar(0, false, false, false); // Reset progress bar if (dom.logContent) dom.logContent.textContent = ''; - // updateQueueInfo(0); // Nicht mehr direkt hier benötigt - if (dom.queueInfo) dom.queueInfo.classList.add('d-none'); // Queue Info Badge ausblenden + if (dom.queueInfo) dom.queueInfo.classList.add('d-none'); + + updateAllStatusMessages('Bereit.'); + updateAllProgressBars(0, false, false, false); + currentJobId = null; isPolling = false; + hideProcessingOverlay(); stopPolling(); console.log("UI State reset."); } - // (Unverändert) function setUIProcessing(isStarting = false) { - if (dom.submitButton) { - dom.submitButton.disabled = true; - } - if (dom.statusMessage) dom.statusMessage.textContent = 'Sende Auftrag...'; + if (dom.submitButton) dom.submitButton.disabled = true; if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); + + updateAllStatusMessages('Sende Auftrag...'); if (isStarting) { if (dom.resultUrlArea) dom.resultUrlArea.classList.add('d-none'); if (dom.logContent) dom.logContent.textContent = ''; - updateProgressBar(0, false, false, false); + updateAllProgressBars(0, false, false, false); } } - // (Unverändert - von vorheriger Lösung) - function updateProgressBar(value, isError = false, isRunning = false, isQueued = false) { - if (!dom.progressBar) return; + // NEU: Funktion, die BEIDE Fortschrittsbalken aktualisiert + function updateAllProgressBars(value, isError = false, isRunning = false, isQueued = false) { const percentage = Math.max(0, Math.min(100, Math.round(value))); - dom.progressBar.style.width = `${percentage}%`; - dom.progressBar.textContent = `${percentage}%`; - dom.progressBar.setAttribute('aria-valuenow', percentage); + + // Funktion zur Aktualisierung eines einzelnen Balkens + const updateSingleBar = (barElement) => { + if (!barElement) return; + barElement.style.width = `${percentage}%`; + barElement.textContent = `${percentage}%`; + + // Spezifische Klassen für den Haupt-Balken + if (barElement.id === 'progress-bar') { + barElement.setAttribute('aria-valuenow', percentage); + barElement.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-secondary', 'progress-bar-animated', 'progress-bar-striped'); + if (isError) barElement.classList.add('bg-danger'); + else if (isQueued) barElement.classList.add('bg-secondary'); + else if (percentage === 100 && !isRunning) barElement.classList.add('bg-success'); + else if (isRunning) barElement.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated'); + else barElement.classList.add('bg-info'); + } - dom.progressBar.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-secondary', 'progress-bar-animated', 'progress-bar-striped'); + // Spezifische Klassen für den Overlay-Balken (hat eigene CSS-Stile) + if (barElement.id === 'overlay-progress-bar') { + barElement.classList.remove('progress-bar-striped', 'progress-bar-animated'); + if (isRunning) { + barElement.classList.add('progress-bar-striped', 'progress-bar-animated'); + } + } + }; - if (isError) { - dom.progressBar.classList.add('bg-danger'); - dom.progressBar.textContent = 'Fehler'; - } else if (isQueued) { - dom.progressBar.classList.add('bg-secondary'); - dom.progressBar.textContent = 'Wartet...'; - } else if (percentage === 100 && !isRunning) { - dom.progressBar.classList.add('bg-success'); - } else if (isRunning) { - dom.progressBar.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated'); - } else { - dom.progressBar.classList.add('bg-info'); - } + updateSingleBar(dom.progressBar); + updateSingleBar(dom.overlayProgressBar); } - // --- NEU: updateStatusMessage wird jetzt die Queue-Position anzeigen --- - function updateStatusMessage(message, position = null, totalQueued = null) { - if (!dom.statusMessage) return; + // NEU: Funktion, die BEIDE Status-Nachrichten aktualisiert + function updateAllStatusMessages(message, position = null, totalQueued = null) { let displayMessage = message || '...'; - // Wenn Positionsdaten vorhanden sind, füge sie hinzu if (position !== null && totalQueued !== null) { displayMessage = `${message} (Position ${position} von ${totalQueued})`; } - dom.statusMessage.textContent = displayMessage; + if (dom.statusMessage) dom.statusMessage.textContent = displayMessage; + if (dom.overlayStatusText) dom.overlayStatusText.textContent = displayMessage; } - // updateQueueInfo wird nicht mehr für die Position verwendet - // function updateQueueInfo(queueSize) { ... } - - // (Unverändert) function appendLog(message, type = 'log') { if (!dom.logContent || !message) return; const timestamp = new Date().toLocaleTimeString(); @@ -178,11 +185,10 @@ document.addEventListener('DOMContentLoaded', () => { else if (type === 'warn') console.warn(message); } - // (Unverändert) function showError(message) { if (!dom.errorMessage) return; let displayMessage = message || "Unbekannter Fehler."; - // Spezifische Fehlertexte verbessern (unverändert) + // ... (Fehlertext-Logik bleibt unverändert) const lowerCaseMessage = message ? message.toLowerCase() : ""; if (lowerCaseMessage.includes("login is required") || lowerCaseMessage.includes("age-restricted") || lowerCaseMessage.includes("instagramloginrequired") || lowerCaseMessage.includes("twitterloginrequired")) { displayMessage = "Dieser Inhalt erfordert eine Anmeldung oder ist altersbeschränkt und kann nicht direkt heruntergeladen werden."; @@ -195,26 +201,25 @@ document.addEventListener('DOMContentLoaded', () => { } else if (lowerCaseMessage.includes("404") || lowerCaseMessage.includes("not found")) { displayMessage = "Fehler: Inhalt nicht gefunden (404)."; } else if (lowerCaseMessage.includes("already processed")) { - displayMessage = message; // Behalte Servernachricht + displayMessage = message; } else if (lowerCaseMessage.includes("worker-fehler") || lowerCaseMessage.includes("schwerer worker-fehler")) { displayMessage = `Interner Serverfehler: ${message}`; } else if (lowerCaseMessage.includes("job nicht gefunden")) { - displayMessage = message; // Behalte Servernachricht + displayMessage = message; } else if (lowerCaseMessage.includes("ungültige url für instagram")) { displayMessage = "Fehler: Ungültige URL für Instagram. Es werden nur Reel-Links unterstützt (z.B. .../reel/...)."; } else if (lowerCaseMessage.includes("ungültige url für twitter")) { displayMessage = "Fehler: Ungültige URL für Twitter/X. Es werden nur Tweet-Links unterstützt (z.B. .../status/...)."; } - dom.errorMessage.textContent = displayMessage; dom.errorMessage.classList.remove('d-none'); - updateStatusMessage('Fehler!'); // Fehlerstatus ohne Position anzeigen - updateProgressBar(dom.progressBar ? parseInt(dom.progressBar.getAttribute('aria-valuenow')) : 0, true, false, false); + updateAllStatusMessages('Fehler!'); + const currentProgress = dom.progressBar ? parseInt(dom.progressBar.getAttribute('aria-valuenow')) : 0; + updateAllProgressBars(currentProgress, true, false, false); appendLog(`Fehler angezeigt: ${displayMessage}`, 'error'); } - // (Unverändert) function showResult(url) { if (!dom.resultUrlArea || !dom.resultUrlLink || !url) return; dom.resultUrlLink.href = url; @@ -223,23 +228,32 @@ document.addEventListener('DOMContentLoaded', () => { if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); } - // --- Dynamische Optionen --- - // (Unverändert) + // --- Overlay Funktionen --- + function showProcessingOverlay() { + if (!dom.processingOverlay || !dom.overlayMessage) return; + const randomIndex = Math.floor(Math.random() * memeMessages.length); + dom.overlayMessage.textContent = memeMessages[randomIndex]; + dom.processingOverlay.classList.add('visible'); + } + + function hideProcessingOverlay() { + if (!dom.processingOverlay) return; + dom.processingOverlay.classList.remove('visible'); + } + + // --- Dynamische Optionen (unverändert) --- function updateDynamicOptionsVisibility() { const selectedPlatform = document.querySelector('input[name="platform"]:checked')?.value; if (!dom.ytOptionsDiv || !dom.codecOptionsSection || !dom.urlHelpText) return; - dom.ytOptionsDiv.classList.add('d-none'); dom.codecOptionsSection.classList.add('d-none'); if(dom.mp3QualitySection) dom.mp3QualitySection.classList.add('d-none'); if(dom.mp4QualitySection) dom.mp4QualitySection.classList.add('d-none'); - if (selectedPlatform === 'Instagram' || selectedPlatform === 'Twitter') { dom.urlHelpText.classList.remove('d-none'); } else { dom.urlHelpText.classList.add('d-none'); } - if (selectedPlatform === 'YouTube') { dom.ytOptionsDiv.classList.remove('d-none'); updateYoutubeQualityVisibility(); @@ -248,17 +262,13 @@ document.addEventListener('DOMContentLoaded', () => { dom.codecOptionsSection.classList.remove('d-none'); } } - - // (Unverändert) function updateYoutubeQualityVisibility() { const selectedFormat = document.querySelector('input[name="yt_format"]:checked')?.value; if (!dom.mp3QualitySection || !dom.mp4QualitySection || !dom.ytQualityDiv || !dom.codecOptionsSection) return; - dom.ytQualityDiv.classList.remove('d-none'); dom.mp3QualitySection.classList.add('d-none'); dom.mp4QualitySection.classList.add('d-none'); dom.codecOptionsSection.classList.add('d-none'); - if (selectedFormat === 'mp3') { dom.mp3QualitySection.classList.remove('d-none'); } else if (selectedFormat === 'mp4') { @@ -268,7 +278,6 @@ document.addEventListener('DOMContentLoaded', () => { } // --- Event Handler --- - // (Unverändert) async function handleFormSubmit(event) { event.preventDefault(); if (isPolling) { @@ -277,13 +286,14 @@ document.addEventListener('DOMContentLoaded', () => { } resetUIState(); setUIProcessing(true); + showProcessingOverlay(); const formData = new FormData(dom.form); try { const response = await fetch('/start_download', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok && result.job_id) { currentJobId = result.job_id; - updateStatusMessage(result.message || 'Auftrag gesendet...'); // Initiale Meldung ohne Position + updateAllStatusMessages(result.message || 'Auftrag gesendet...'); appendLog(`Auftrag ${currentJobId} gestartet: ${result.message || ''}`, 'info'); startPolling(); } else { @@ -295,8 +305,6 @@ document.addEventListener('DOMContentLoaded', () => { resetUIState(); } } - - // (Unverändert) async function handleClearHistory() { if (!dom.clearHistoryButton || !dom.historyTableBody) return; if (confirm('Möchtest du wirklich den gesamten Verlauf löschen?')) { @@ -318,12 +326,8 @@ document.addEventListener('DOMContentLoaded', () => { } // --- Polling --- - // (Unverändert) function startPolling() { - if (!currentJobId) { - console.warn("StartPolling ohne Job ID aufgerufen."); - return; - } + if (!currentJobId) return; stopPolling(); isPolling = true; if (dom.submitButton) { @@ -331,11 +335,10 @@ document.addEventListener('DOMContentLoaded', () => { dom.submitButton.disabled = true; } pollingInterval = setInterval(fetchStatus, 2000); - fetchStatus(); // Fetch immediately once + fetchStatus(); console.log(`Polling gestartet für Job ${currentJobId}.`); } - // (Unverändert) function stopPolling() { if (pollingInterval) { clearInterval(pollingInterval); @@ -345,42 +348,28 @@ document.addEventListener('DOMContentLoaded', () => { } } - // --- fetchStatus: CORE CHANGE HERE --- async function fetchStatus() { if (!currentJobId || !isPolling) { - console.log("FetchStatus abgebrochen (keine JobID oder Polling inaktiv)."); stopPolling(); return; } - - console.log(`Fetching status for job ${currentJobId}...`); - try { const response = await fetch(`/status?job_id=${currentJobId}`); - if (response.status === 404) { const status = await response.json(); - console.warn(`Job ${currentJobId} nicht gefunden (Status 404). Status:`, status); showError(status.error || "Auftrag nicht gefunden (möglicherweise zu alt)."); stopPolling(); + hideProcessingOverlay(); resetSubmitButton(); return; } - - if (!response.ok) { - throw new Error(`Status-Serverfehler: ${response.status}`); - } + if (!response.ok) throw new Error(`Status-Serverfehler: ${response.status}`); + const status = await response.json(); - console.log("Received status:", status); - - // --- UI Updates basierend auf dem Job-Status --- - try { - if (dom.logContent && Array.isArray(status.logs)) { - dom.logContent.textContent = status.logs.join('\n') + '\n'; - if (dom.logOutput) dom.logOutput.scrollTop = dom.logOutput.scrollHeight; - } - } catch (logError) { - console.error("Fehler beim Aktualisieren der Logs:", logError); + + if (dom.logContent && Array.isArray(status.logs)) { + dom.logContent.textContent = status.logs.join('\n') + '\n'; + if (dom.logOutput) dom.logOutput.scrollTop = dom.logOutput.scrollHeight; } const isRunning = status.running === true; @@ -389,72 +378,53 @@ document.addEventListener('DOMContentLoaded', () => { const isError = !!status.error || status.status === 'error'; const isNotFound = status.status === 'not_found'; - // Update Progress Bar mit allen Zuständen - updateProgressBar(status.progress || 0, isError, isRunning, isQueued); - - // --- NEU: Update Status Message mit Positionsinfo --- + // Status und Fortschritt an beide UI-Teile senden + updateAllProgressBars(status.progress || 0, isError, isRunning, isQueued); if (isQueued && status.position !== undefined && status.total_queued !== undefined) { - updateStatusMessage(status.message || 'In Warteschlange...', status.position, status.total_queued); + updateAllStatusMessages(status.message || 'In Warteschlange...', status.position, status.total_queued); } else { - updateStatusMessage(status.message || '...'); // Normale Nachricht ohne Position + updateAllStatusMessages(status.message || '...'); } - // --- ENDE NEU --- - // Queue Info Badge nicht mehr verwenden - // updateQueueInfo(status.queue_size || 0); - if (dom.queueInfo) dom.queueInfo.classList.add('d-none'); - - - // --- Logik zum Stoppen des Pollings --- if (isCompleted || isError || isNotFound) { - console.log(`Job ${currentJobId} ist beendet. Status: ${status.status}`); stopPolling(); + hideProcessingOverlay(); if (isError) { - console.error(`Backend meldet Fehler für Job ${currentJobId}:`, status.error || status.message); showError(status.error || status.message); } else if (isCompleted) { - updateStatusMessage('Abgeschlossen!'); // Finale Meldung ohne Position - updateProgressBar(100, false, false, false); - if (status.result_url) { - showResult(status.result_url); - } + updateAllStatusMessages('Abgeschlossen!'); + updateAllProgressBars(100, false, false, false); + if (status.result_url) showResult(status.result_url); if (historyEnabled) fetchHistory(); fetchStats(); } else { showError(status.error || status.message || "Auftrag beendet, aber Status unklar."); } resetSubmitButton(); - - } else if (isRunning || isQueued) { // Polling weiterführen wenn running ODER queued + } else if (isRunning || isQueued) { if (dom.submitButton && !dom.submitButton.disabled) { dom.submitButton.innerHTML = ' Verarbeite...'; dom.submitButton.disabled = true; } - } else { - console.warn(`Unerwarteter Job-Status für ${currentJobId}:`, status); } - } catch (error) { console.error('Polling-Fehler:', error); - appendLog(`Polling fehlgeschlagen für Job ${currentJobId}: ${error.message}`, 'error'); - showError(`Polling-Fehler: ${error.message}. Prozess möglicherweise unterbrochen.`); + appendLog(`Polling fehlgeschlagen: ${error.message}`, 'error'); + showError(`Polling-Fehler: ${error.message}.`); stopPolling(); resetUIState(); } } - // (Unverändert) function resetSubmitButton() { if (dom.submitButton) { dom.submitButton.disabled = false; dom.submitButton.innerHTML = ' Download starten'; - console.log("Submit button reset."); } } - // --- History --- - // (Unverändert) + // --- History (unverändert) --- async function fetchHistory() { if (!dom.historyTableBody || !historyEnabled) return; dom.historyTableBody.innerHTML = '
+ Hacking the mainframe...
+ + + +