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 = ' Lade Verlauf...'; @@ -468,8 +438,6 @@ document.addEventListener('DOMContentLoaded', () => { dom.historyTableBody.innerHTML = `Fehler Laden Verlauf: ${error.message}`; } } - - // (Unverändert) function renderHistory(historyData) { if (!dom.historyTableBody || !historyEnabled) return; dom.historyTableBody.innerHTML = ''; @@ -477,47 +445,32 @@ document.addEventListener('DOMContentLoaded', () => { dom.historyTableBody.innerHTML = 'Keine Einträge im Verlauf.'; return; } historyData.forEach(entry => { - try { - const row = dom.historyTableBody.insertRow(); - row.insertCell().textContent = entry.timestamp || ''; - const platformCell = row.insertCell(); platformCell.classList.add('text-center'); - let platformIcon = ''; - const platform = entry.platform || 'Unbekannt'; - if (platform === 'SoundCloud') platformIcon = ''; - else if (platform === 'YouTube') platformIcon = ''; - else if (platform === 'TikTok') platformIcon = ''; - else if (platform === 'Instagram') platformIcon = ''; - else if (platform === 'Twitter') platformIcon = ''; - platformCell.innerHTML = platformIcon; - const titleCell = row.insertCell(); titleCell.textContent = entry.title || 'N/A'; titleCell.title = entry.title || ''; - createLinkCell(row.insertCell(), entry['source_url']); - createLinkCell(row.insertCell(), entry['s3_url']); - } catch (renderError) { - console.error("Fehler Rendern History-Eintrag:", entry, renderError); - const errorRow = dom.historyTableBody.insertRow(); - const cell = errorRow.insertCell(); cell.colSpan = 5; cell.textContent = "Fehler Anzeige Eintrag."; cell.style.color = 'red'; - } + const row = dom.historyTableBody.insertRow(); + row.insertCell().textContent = entry.timestamp || ''; + const platformCell = row.insertCell(); platformCell.classList.add('text-center'); + let platformIcon = ''; + const platform = entry.platform || 'Unbekannt'; + if (platform === 'SoundCloud') platformIcon = ''; + else if (platform === 'YouTube') platformIcon = ''; + else if (platform === 'TikTok') platformIcon = ''; + else if (platform === 'Instagram') platformIcon = ''; + else if (platform === 'Twitter') platformIcon = ''; + platformCell.innerHTML = platformIcon; + const titleCell = row.insertCell(); titleCell.textContent = entry.title || 'N/A'; titleCell.title = entry.title || ''; + createLinkCell(row.insertCell(), entry['source_url']); + createLinkCell(row.insertCell(), entry['s3_url']); }); } - - // (Unverändert) function createLinkCell(cell, url) { - if (!cell || typeof cell.appendChild !== 'function') return; - cell.innerHTML = ''; - const urlString = (url === null || typeof url === 'undefined') ? '' : String(url).trim(); - if (urlString && (urlString.startsWith('http://') || urlString.startsWith('https://'))) { - try { - const link = document.createElement('a'); link.href = urlString; - link.textContent = urlString.length > 50 ? urlString.substring(0, 47) + '...' : urlString; - link.title = urlString; link.target = "_blank"; link.rel = "noopener noreferrer"; - link.dataset.url = urlString; // Für Kontextmenü - cell.appendChild(link); - } catch (e) { console.error(`Fehler Link Erstellung für '${urlString}':`, e); cell.textContent = urlString || 'Fehler'; } - } else { cell.textContent = urlString || 'N/A'; cell.title = urlString; } + if (!cell || !url) { cell.textContent = url || 'N/A'; return; } + const link = document.createElement('a'); link.href = url; + link.textContent = url.length > 50 ? url.substring(0, 47) + '...' : url; + link.title = url; link.target = "_blank"; link.rel = "noopener noreferrer"; + link.dataset.url = url; + cell.appendChild(link); } - // --- Kontextmenü --- - // (Unverändert) + // --- Kontextmenü (unverändert) --- let currentContextMenuUrl = null; function showContextMenu(event, url) { event.preventDefault(); @@ -540,8 +493,7 @@ document.addEventListener('DOMContentLoaded', () => { hideContextMenu(); } - // --- Statistik --- - // (Unverändert) + // --- Statistik (unverändert) --- async function fetchStats() { if (!dom.statsTotalJobs || !dom.statsAvgDuration || !dom.statsTotalSize) return; try { @@ -553,20 +505,11 @@ document.addEventListener('DOMContentLoaded', () => { dom.statsTotalSize.textContent = stats.total_size_formatted ?? 'N/A'; } catch (error) { console.error('Fehler Laden Statistiken:', error); - dom.statsTotalJobs.textContent = 'Fehler'; dom.statsAvgDuration.textContent = 'Fehler'; dom.statsTotalSize.textContent = 'Fehler'; } } - // --- Hilfsfunktionen --- - // (Unverändert) + // --- Hilfsfunktionen (unverändert) --- function copyToClipboard(text) { - if (!navigator.clipboard) { /* Fallback */ - try { - const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; - document.body.appendChild(ta); ta.focus(); ta.select(); document.execCommand('copy'); - document.body.removeChild(ta); appendLog("Link kopiert (Fallback).", "info"); - } catch (err) { console.error('Fallback Copy failed:', err); alert("Kopieren fehlgeschlagen."); } return; - } navigator.clipboard.writeText(text).then(() => appendLog("Link kopiert.", "info")) .catch(err => { console.error('Async Copy failed:', err); alert("Kopieren fehlgeschlagen."); }); } diff --git a/static/style.css b/static/style.css index 17d9ad9..9cf347d 100644 --- a/static/style.css +++ b/static/style.css @@ -165,4 +165,78 @@ body { /* Hilfetext für URL-Eingabe */ #urlHelp { font-size: 0.8rem; +} + +/* --- Fullscreen Processing Overlay --- */ +#processing-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(10, 25, 47, 0.85); /* Dunkelblauer, transparenter Hintergrund */ + backdrop-filter: blur(5px); /* Hintergrund unscharf machen */ + -webkit-backdrop-filter: blur(5px); /* Für Safari-Kompatibilität */ + z-index: 2000; /* Über allem anderen */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #64ffda; /* Cyan/Türkis für den Text */ + font-family: 'Courier New', Courier, monospace; + text-align: center; + opacity: 0; + visibility: hidden; + transition: opacity 0.4s ease, visibility 0.4s ease; +} + +#processing-overlay.visible { + opacity: 1; + visibility: visible; +} + +#processing-overlay img { + max-width: 90%; + width: 350px; + height: auto; + border-radius: 8px; + box-shadow: 0 0 25px rgba(100, 255, 218, 0.5); /* Passender Leuchteffekt */ + margin-bottom: 20px; +} + +#processing-overlay #overlay-message { + font-size: 1.5rem; + text-shadow: 0 0 10px #64ffda; /* Leuchteffekt für Text */ + padding: 0 20px; + margin-bottom: 25px; /* Mehr Abstand nach unten */ +} + +/* NEU: Stile für den Status-Container im Overlay */ +.overlay-status-container { + width: 80%; + max-width: 600px; +} + +#overlay-status-text { + font-size: 1rem; + color: #ccd6f6; /* Heller, aber nicht so grell wie die Hauptfarbe */ + margin-bottom: 10px; + min-height: 1.2em; /* Verhindert Springen bei Textänderung */ + word-wrap: break-word; +} + +/* NEU: Stile für den Fortschrittsbalken im Overlay */ +#processing-overlay .progress { + height: 20px; + background-color: rgba(100, 255, 218, 0.1); /* Hintergrund des Balkens */ + border: 1px solid rgba(100, 255, 218, 0.3); + border-radius: 5px; + padding: 2px; +} + +#processing-overlay .progress-bar { + background-color: #64ffda !important; /* Wichtig, um Bootstrap zu überschreiben */ + color: #0a192f; /* Dunkler Text für Kontrast */ + font-weight: bold; + transition: width 0.3s ease-in-out; /* Weicherer Übergang */ } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index abfcb86..7c74c6d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,4 +1,3 @@ - @@ -247,6 +246,20 @@ + +
+ Processing Animation +

Hacking the mainframe...

+ + +
+

Initialisiere...

+
+
0%
+
+
+
+