add meme side :P

This commit is contained in:
2025-09-24 11:29:03 +02:00
parent e0e70f2ed4
commit d024aec2af
3 changed files with 226 additions and 196 deletions
+136 -193
View File
@@ -9,41 +9,59 @@ document.addEventListener('DOMContentLoaded', () => {
ytFormatRadios: 'input[name="yt_format"]', ytFormatRadios: 'input[name="yt_format"]',
mp3QualitySection: '#mp3-quality-section', mp3QualitySection: '#mp3-quality-section',
mp4QualitySection: '#mp4-quality-section', mp4QualitySection: '#mp4-quality-section',
codecOptionsSection: '#codec-options-section', // Für Video Codec codecOptionsSection: '#codec-options-section',
progressBar: '#progress-bar', progressBar: '#progress-bar',
statusMessage: '#status-message', statusMessage: '#status-message',
logContent: '#log-content', logContent: '#log-content',
logOutput: '#log-output', logOutput: '#log-output',
resultUrlArea: '#result-url-area', resultUrlArea: '#result-url-area',
resultUrlLink: '#result-url', resultUrlLink: '#result-url',
copyResultUrlLink: '#copy-result-url', // Nicht mehr im HTML, aber lassen wir es hier copyResultUrlLink: '#copy-result-url',
errorMessage: '#error-message', errorMessage: '#error-message',
historyTableBody: '#history-table tbody', historyTableBody: '#history-table tbody',
clearHistoryButton: '#clear-history-button', clearHistoryButton: '#clear-history-button',
contextMenu: '#context-menu', contextMenu: '#context-menu',
queueInfo: '#queue-info', // Behalten wir für evtl. spätere Nutzung queueInfo: '#queue-info',
statsTotalJobs: '#stats-total-jobs', statsTotalJobs: '#stats-total-jobs',
statsAvgDuration: '#stats-avg-duration', statsAvgDuration: '#stats-avg-duration',
statsTotalSize: '#stats-total-size', statsTotalSize: '#stats-total-size',
urlHelpText: '#urlHelp', 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 pollingInterval = null;
let currentJobId = null; // Aktuelle Job-ID speichern let currentJobId = null;
let isPolling = false; // Separater State für aktives Polling let isPolling = false;
const historyEnabled = !!document.querySelector(selectors.clearHistoryButton); const historyEnabled = !!document.querySelector(selectors.clearHistoryButton);
const videoPlatforms = ['YouTube', 'TikTok', 'Instagram', 'Twitter']; 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 --- // --- Initialisierung ---
// (Unverändert)
function init() { function init() {
for (const key in selectors) { for (const key in selectors) {
dom[key] = document.querySelector(selectors[key]); dom[key] = document.querySelector(selectors[key]);
if (!dom[key] && ['form', 'submitButton', 'statusMessage', 'progressBar', 'logContent', 'errorMessage', 'queueInfo', 'statsTotalJobs', 'statsAvgDuration', 'statsTotalSize', 'codecOptionsSection', 'urlHelpText'].includes(key)) { if (!dom[key] && !['copyResultUrlLink', 'clearHistoryButton', 'historyTableBody', 'contextMenu'].includes(key)) {
console.warn(`Optionales DOM-Element nicht gefunden: ${selectors[key]}`); console.warn(`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]}`);
} }
} }
dom.platformRadios = document.querySelectorAll(selectors.platformRadios); dom.platformRadios = document.querySelectorAll(selectors.platformRadios);
@@ -60,29 +78,15 @@ document.addEventListener('DOMContentLoaded', () => {
} }
resetUIState(); resetUIState();
fetchStats(); fetchStats();
console.log("Uploader UI initialisiert."); console.log("Uploader UI initialisiert.");
console.log("History aktiviert (Frontend):", historyEnabled);
} }
// --- Event Listener Setup --- // --- Event Listener Setup ---
// (Unverändert)
function setupEventListeners() { function setupEventListeners() {
if (dom.form) dom.form.addEventListener('submit', handleFormSubmit); if (dom.form) dom.form.addEventListener('submit', handleFormSubmit);
dom.platformRadios.forEach(radio => radio.addEventListener('change', updateDynamicOptionsVisibility)); dom.platformRadios.forEach(radio => radio.addEventListener('change', updateDynamicOptionsVisibility));
dom.ytFormatRadios.forEach(radio => radio.addEventListener('change', updateYoutubeQualityVisibility)); dom.ytFormatRadios.forEach(radio => radio.addEventListener('change', updateYoutubeQualityVisibility));
if (dom.clearHistoryButton) dom.clearHistoryButton.addEventListener('click', handleClearHistory); 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); document.addEventListener('click', hideContextMenu);
if (dom.contextMenu) dom.contextMenu.addEventListener('click', handleContextMenuClick); if (dom.contextMenu) dom.contextMenu.addEventListener('click', handleContextMenuClick);
if (dom.historyTableBody && historyEnabled) { if (dom.historyTableBody && historyEnabled) {
@@ -94,80 +98,83 @@ document.addEventListener('DOMContentLoaded', () => {
} }
// --- UI Update Funktionen --- // --- UI Update Funktionen ---
// (Unverändert)
function resetUIState() { function resetUIState() {
if (dom.submitButton) { if (dom.submitButton) {
dom.submitButton.disabled = false; dom.submitButton.disabled = false;
dom.submitButton.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Download starten'; dom.submitButton.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Download starten';
} }
if (dom.statusMessage) dom.statusMessage.textContent = 'Bereit.';
if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); if (dom.errorMessage) dom.errorMessage.classList.add('d-none');
if (dom.resultUrlArea) dom.resultUrlArea.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 = ''; if (dom.logContent) dom.logContent.textContent = '';
// updateQueueInfo(0); // Nicht mehr direkt hier benötigt if (dom.queueInfo) dom.queueInfo.classList.add('d-none');
if (dom.queueInfo) dom.queueInfo.classList.add('d-none'); // Queue Info Badge ausblenden
updateAllStatusMessages('Bereit.');
updateAllProgressBars(0, false, false, false);
currentJobId = null; currentJobId = null;
isPolling = false; isPolling = false;
hideProcessingOverlay();
stopPolling(); stopPolling();
console.log("UI State reset."); console.log("UI State reset.");
} }
// (Unverändert)
function setUIProcessing(isStarting = false) { function setUIProcessing(isStarting = false) {
if (dom.submitButton) { if (dom.submitButton) dom.submitButton.disabled = true;
dom.submitButton.disabled = true;
}
if (dom.statusMessage) dom.statusMessage.textContent = 'Sende Auftrag...';
if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); if (dom.errorMessage) dom.errorMessage.classList.add('d-none');
updateAllStatusMessages('Sende Auftrag...');
if (isStarting) { if (isStarting) {
if (dom.resultUrlArea) dom.resultUrlArea.classList.add('d-none'); if (dom.resultUrlArea) dom.resultUrlArea.classList.add('d-none');
if (dom.logContent) dom.logContent.textContent = ''; if (dom.logContent) dom.logContent.textContent = '';
updateProgressBar(0, false, false, false); updateAllProgressBars(0, false, false, false);
} }
} }
// (Unverändert - von vorheriger Lösung) // NEU: Funktion, die BEIDE Fortschrittsbalken aktualisiert
function updateProgressBar(value, isError = false, isRunning = false, isQueued = false) { function updateAllProgressBars(value, isError = false, isRunning = false, isQueued = false) {
if (!dom.progressBar) return;
const percentage = Math.max(0, Math.min(100, Math.round(value))); 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);
dom.progressBar.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-secondary', 'progress-bar-animated', 'progress-bar-striped'); // Funktion zur Aktualisierung eines einzelnen Balkens
const updateSingleBar = (barElement) => {
if (!barElement) return;
barElement.style.width = `${percentage}%`;
barElement.textContent = `${percentage}%`;
if (isError) { // Spezifische Klassen für den Haupt-Balken
dom.progressBar.classList.add('bg-danger'); if (barElement.id === 'progress-bar') {
dom.progressBar.textContent = 'Fehler'; barElement.setAttribute('aria-valuenow', percentage);
} else if (isQueued) { barElement.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-secondary', 'progress-bar-animated', 'progress-bar-striped');
dom.progressBar.classList.add('bg-secondary'); if (isError) barElement.classList.add('bg-danger');
dom.progressBar.textContent = 'Wartet...'; else if (isQueued) barElement.classList.add('bg-secondary');
} else if (percentage === 100 && !isRunning) { else if (percentage === 100 && !isRunning) barElement.classList.add('bg-success');
dom.progressBar.classList.add('bg-success'); else if (isRunning) barElement.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated');
} else if (isRunning) { else barElement.classList.add('bg-info');
dom.progressBar.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated'); }
} else {
dom.progressBar.classList.add('bg-info'); // 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');
}
}
};
updateSingleBar(dom.progressBar);
updateSingleBar(dom.overlayProgressBar);
} }
// --- NEU: updateStatusMessage wird jetzt die Queue-Position anzeigen --- // NEU: Funktion, die BEIDE Status-Nachrichten aktualisiert
function updateStatusMessage(message, position = null, totalQueued = null) { function updateAllStatusMessages(message, position = null, totalQueued = null) {
if (!dom.statusMessage) return;
let displayMessage = message || '...'; let displayMessage = message || '...';
// Wenn Positionsdaten vorhanden sind, füge sie hinzu
if (position !== null && totalQueued !== null) { if (position !== null && totalQueued !== null) {
displayMessage = `${message} (Position ${position} von ${totalQueued})`; 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') { function appendLog(message, type = 'log') {
if (!dom.logContent || !message) return; if (!dom.logContent || !message) return;
const timestamp = new Date().toLocaleTimeString(); const timestamp = new Date().toLocaleTimeString();
@@ -178,11 +185,10 @@ document.addEventListener('DOMContentLoaded', () => {
else if (type === 'warn') console.warn(message); else if (type === 'warn') console.warn(message);
} }
// (Unverändert)
function showError(message) { function showError(message) {
if (!dom.errorMessage) return; if (!dom.errorMessage) return;
let displayMessage = message || "Unbekannter Fehler."; let displayMessage = message || "Unbekannter Fehler.";
// Spezifische Fehlertexte verbessern (unverändert) // ... (Fehlertext-Logik bleibt unverändert)
const lowerCaseMessage = message ? message.toLowerCase() : ""; const lowerCaseMessage = message ? message.toLowerCase() : "";
if (lowerCaseMessage.includes("login is required") || lowerCaseMessage.includes("age-restricted") || lowerCaseMessage.includes("instagramloginrequired") || lowerCaseMessage.includes("twitterloginrequired")) { 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."; 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")) { } else if (lowerCaseMessage.includes("404") || lowerCaseMessage.includes("not found")) {
displayMessage = "Fehler: Inhalt nicht gefunden (404)."; displayMessage = "Fehler: Inhalt nicht gefunden (404).";
} else if (lowerCaseMessage.includes("already processed")) { } else if (lowerCaseMessage.includes("already processed")) {
displayMessage = message; // Behalte Servernachricht displayMessage = message;
} else if (lowerCaseMessage.includes("worker-fehler") || lowerCaseMessage.includes("schwerer worker-fehler")) { } else if (lowerCaseMessage.includes("worker-fehler") || lowerCaseMessage.includes("schwerer worker-fehler")) {
displayMessage = `Interner Serverfehler: ${message}`; displayMessage = `Interner Serverfehler: ${message}`;
} else if (lowerCaseMessage.includes("job nicht gefunden")) { } else if (lowerCaseMessage.includes("job nicht gefunden")) {
displayMessage = message; // Behalte Servernachricht displayMessage = message;
} else if (lowerCaseMessage.includes("ungültige url für instagram")) { } 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/...)."; 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")) { } 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/...)."; 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.textContent = displayMessage;
dom.errorMessage.classList.remove('d-none'); dom.errorMessage.classList.remove('d-none');
updateStatusMessage('Fehler!'); // Fehlerstatus ohne Position anzeigen updateAllStatusMessages('Fehler!');
updateProgressBar(dom.progressBar ? parseInt(dom.progressBar.getAttribute('aria-valuenow')) : 0, true, false, false); const currentProgress = dom.progressBar ? parseInt(dom.progressBar.getAttribute('aria-valuenow')) : 0;
updateAllProgressBars(currentProgress, true, false, false);
appendLog(`Fehler angezeigt: ${displayMessage}`, 'error'); appendLog(`Fehler angezeigt: ${displayMessage}`, 'error');
} }
// (Unverändert)
function showResult(url) { function showResult(url) {
if (!dom.resultUrlArea || !dom.resultUrlLink || !url) return; if (!dom.resultUrlArea || !dom.resultUrlLink || !url) return;
dom.resultUrlLink.href = url; dom.resultUrlLink.href = url;
@@ -223,23 +228,32 @@ document.addEventListener('DOMContentLoaded', () => {
if (dom.errorMessage) dom.errorMessage.classList.add('d-none'); if (dom.errorMessage) dom.errorMessage.classList.add('d-none');
} }
// --- Dynamische Optionen --- // --- Overlay Funktionen ---
// (Unverändert) 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() { function updateDynamicOptionsVisibility() {
const selectedPlatform = document.querySelector('input[name="platform"]:checked')?.value; const selectedPlatform = document.querySelector('input[name="platform"]:checked')?.value;
if (!dom.ytOptionsDiv || !dom.codecOptionsSection || !dom.urlHelpText) return; if (!dom.ytOptionsDiv || !dom.codecOptionsSection || !dom.urlHelpText) return;
dom.ytOptionsDiv.classList.add('d-none'); dom.ytOptionsDiv.classList.add('d-none');
dom.codecOptionsSection.classList.add('d-none'); dom.codecOptionsSection.classList.add('d-none');
if(dom.mp3QualitySection) dom.mp3QualitySection.classList.add('d-none'); if(dom.mp3QualitySection) dom.mp3QualitySection.classList.add('d-none');
if(dom.mp4QualitySection) dom.mp4QualitySection.classList.add('d-none'); if(dom.mp4QualitySection) dom.mp4QualitySection.classList.add('d-none');
if (selectedPlatform === 'Instagram' || selectedPlatform === 'Twitter') { if (selectedPlatform === 'Instagram' || selectedPlatform === 'Twitter') {
dom.urlHelpText.classList.remove('d-none'); dom.urlHelpText.classList.remove('d-none');
} else { } else {
dom.urlHelpText.classList.add('d-none'); dom.urlHelpText.classList.add('d-none');
} }
if (selectedPlatform === 'YouTube') { if (selectedPlatform === 'YouTube') {
dom.ytOptionsDiv.classList.remove('d-none'); dom.ytOptionsDiv.classList.remove('d-none');
updateYoutubeQualityVisibility(); updateYoutubeQualityVisibility();
@@ -248,17 +262,13 @@ document.addEventListener('DOMContentLoaded', () => {
dom.codecOptionsSection.classList.remove('d-none'); dom.codecOptionsSection.classList.remove('d-none');
} }
} }
// (Unverändert)
function updateYoutubeQualityVisibility() { function updateYoutubeQualityVisibility() {
const selectedFormat = document.querySelector('input[name="yt_format"]:checked')?.value; const selectedFormat = document.querySelector('input[name="yt_format"]:checked')?.value;
if (!dom.mp3QualitySection || !dom.mp4QualitySection || !dom.ytQualityDiv || !dom.codecOptionsSection) return; if (!dom.mp3QualitySection || !dom.mp4QualitySection || !dom.ytQualityDiv || !dom.codecOptionsSection) return;
dom.ytQualityDiv.classList.remove('d-none'); dom.ytQualityDiv.classList.remove('d-none');
dom.mp3QualitySection.classList.add('d-none'); dom.mp3QualitySection.classList.add('d-none');
dom.mp4QualitySection.classList.add('d-none'); dom.mp4QualitySection.classList.add('d-none');
dom.codecOptionsSection.classList.add('d-none'); dom.codecOptionsSection.classList.add('d-none');
if (selectedFormat === 'mp3') { if (selectedFormat === 'mp3') {
dom.mp3QualitySection.classList.remove('d-none'); dom.mp3QualitySection.classList.remove('d-none');
} else if (selectedFormat === 'mp4') { } else if (selectedFormat === 'mp4') {
@@ -268,7 +278,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
// --- Event Handler --- // --- Event Handler ---
// (Unverändert)
async function handleFormSubmit(event) { async function handleFormSubmit(event) {
event.preventDefault(); event.preventDefault();
if (isPolling) { if (isPolling) {
@@ -277,13 +286,14 @@ document.addEventListener('DOMContentLoaded', () => {
} }
resetUIState(); resetUIState();
setUIProcessing(true); setUIProcessing(true);
showProcessingOverlay();
const formData = new FormData(dom.form); const formData = new FormData(dom.form);
try { try {
const response = await fetch('/start_download', { method: 'POST', body: formData }); const response = await fetch('/start_download', { method: 'POST', body: formData });
const result = await response.json(); const result = await response.json();
if (response.ok && result.job_id) { if (response.ok && result.job_id) {
currentJobId = 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'); appendLog(`Auftrag ${currentJobId} gestartet: ${result.message || ''}`, 'info');
startPolling(); startPolling();
} else { } else {
@@ -295,8 +305,6 @@ document.addEventListener('DOMContentLoaded', () => {
resetUIState(); resetUIState();
} }
} }
// (Unverändert)
async function handleClearHistory() { async function handleClearHistory() {
if (!dom.clearHistoryButton || !dom.historyTableBody) return; if (!dom.clearHistoryButton || !dom.historyTableBody) return;
if (confirm('Möchtest du wirklich den gesamten Verlauf löschen?')) { if (confirm('Möchtest du wirklich den gesamten Verlauf löschen?')) {
@@ -318,12 +326,8 @@ document.addEventListener('DOMContentLoaded', () => {
} }
// --- Polling --- // --- Polling ---
// (Unverändert)
function startPolling() { function startPolling() {
if (!currentJobId) { if (!currentJobId) return;
console.warn("StartPolling ohne Job ID aufgerufen.");
return;
}
stopPolling(); stopPolling();
isPolling = true; isPolling = true;
if (dom.submitButton) { if (dom.submitButton) {
@@ -331,11 +335,10 @@ document.addEventListener('DOMContentLoaded', () => {
dom.submitButton.disabled = true; dom.submitButton.disabled = true;
} }
pollingInterval = setInterval(fetchStatus, 2000); pollingInterval = setInterval(fetchStatus, 2000);
fetchStatus(); // Fetch immediately once fetchStatus();
console.log(`Polling gestartet für Job ${currentJobId}.`); console.log(`Polling gestartet für Job ${currentJobId}.`);
} }
// (Unverändert)
function stopPolling() { function stopPolling() {
if (pollingInterval) { if (pollingInterval) {
clearInterval(pollingInterval); clearInterval(pollingInterval);
@@ -345,42 +348,28 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
// --- fetchStatus: CORE CHANGE HERE ---
async function fetchStatus() { async function fetchStatus() {
if (!currentJobId || !isPolling) { if (!currentJobId || !isPolling) {
console.log("FetchStatus abgebrochen (keine JobID oder Polling inaktiv).");
stopPolling(); stopPolling();
return; return;
} }
console.log(`Fetching status for job ${currentJobId}...`);
try { try {
const response = await fetch(`/status?job_id=${currentJobId}`); const response = await fetch(`/status?job_id=${currentJobId}`);
if (response.status === 404) { if (response.status === 404) {
const status = await response.json(); 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)."); showError(status.error || "Auftrag nicht gefunden (möglicherweise zu alt).");
stopPolling(); stopPolling();
hideProcessingOverlay();
resetSubmitButton(); resetSubmitButton();
return; 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(); const status = await response.json();
console.log("Received status:", status);
// --- UI Updates basierend auf dem Job-Status --- if (dom.logContent && Array.isArray(status.logs)) {
try { dom.logContent.textContent = status.logs.join('\n') + '\n';
if (dom.logContent && Array.isArray(status.logs)) { if (dom.logOutput) dom.logOutput.scrollTop = dom.logOutput.scrollHeight;
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);
} }
const isRunning = status.running === true; const isRunning = status.running === true;
@@ -389,72 +378,53 @@ document.addEventListener('DOMContentLoaded', () => {
const isError = !!status.error || status.status === 'error'; const isError = !!status.error || status.status === 'error';
const isNotFound = status.status === 'not_found'; const isNotFound = status.status === 'not_found';
// Update Progress Bar mit allen Zuständen // Status und Fortschritt an beide UI-Teile senden
updateProgressBar(status.progress || 0, isError, isRunning, isQueued); updateAllProgressBars(status.progress || 0, isError, isRunning, isQueued);
// --- NEU: Update Status Message mit Positionsinfo ---
if (isQueued && status.position !== undefined && status.total_queued !== undefined) { 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 { } 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) { if (isCompleted || isError || isNotFound) {
console.log(`Job ${currentJobId} ist beendet. Status: ${status.status}`);
stopPolling(); stopPolling();
hideProcessingOverlay();
if (isError) { if (isError) {
console.error(`Backend meldet Fehler für Job ${currentJobId}:`, status.error || status.message);
showError(status.error || status.message); showError(status.error || status.message);
} else if (isCompleted) { } else if (isCompleted) {
updateStatusMessage('Abgeschlossen!'); // Finale Meldung ohne Position updateAllStatusMessages('Abgeschlossen!');
updateProgressBar(100, false, false, false); updateAllProgressBars(100, false, false, false);
if (status.result_url) { if (status.result_url) showResult(status.result_url);
showResult(status.result_url);
}
if (historyEnabled) fetchHistory(); if (historyEnabled) fetchHistory();
fetchStats(); fetchStats();
} else { } else {
showError(status.error || status.message || "Auftrag beendet, aber Status unklar."); showError(status.error || status.message || "Auftrag beendet, aber Status unklar.");
} }
resetSubmitButton(); resetSubmitButton();
} else if (isRunning || isQueued) {
} else if (isRunning || isQueued) { // Polling weiterführen wenn running ODER queued
if (dom.submitButton && !dom.submitButton.disabled) { if (dom.submitButton && !dom.submitButton.disabled) {
dom.submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verarbeite...'; dom.submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verarbeite...';
dom.submitButton.disabled = true; dom.submitButton.disabled = true;
} }
} else {
console.warn(`Unerwarteter Job-Status für ${currentJobId}:`, status);
} }
} catch (error) { } catch (error) {
console.error('Polling-Fehler:', error); console.error('Polling-Fehler:', error);
appendLog(`Polling fehlgeschlagen für Job ${currentJobId}: ${error.message}`, 'error'); appendLog(`Polling fehlgeschlagen: ${error.message}`, 'error');
showError(`Polling-Fehler: ${error.message}. Prozess möglicherweise unterbrochen.`); showError(`Polling-Fehler: ${error.message}.`);
stopPolling(); stopPolling();
resetUIState(); resetUIState();
} }
} }
// (Unverändert)
function resetSubmitButton() { function resetSubmitButton() {
if (dom.submitButton) { if (dom.submitButton) {
dom.submitButton.disabled = false; dom.submitButton.disabled = false;
dom.submitButton.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Download starten'; dom.submitButton.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Download starten';
console.log("Submit button reset.");
} }
} }
// --- History --- // --- History (unverändert) ---
// (Unverändert)
async function fetchHistory() { async function fetchHistory() {
if (!dom.historyTableBody || !historyEnabled) return; if (!dom.historyTableBody || !historyEnabled) return;
dom.historyTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-muted"><i class="fas fa-spinner fa-spin"></i> Lade Verlauf...</td></tr>'; dom.historyTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-muted"><i class="fas fa-spinner fa-spin"></i> Lade Verlauf...</td></tr>';
@@ -468,8 +438,6 @@ document.addEventListener('DOMContentLoaded', () => {
dom.historyTableBody.innerHTML = `<tr><td colspan="5" class="text-center text-danger">Fehler Laden Verlauf: ${error.message}</td></tr>`; dom.historyTableBody.innerHTML = `<tr><td colspan="5" class="text-center text-danger">Fehler Laden Verlauf: ${error.message}</td></tr>`;
} }
} }
// (Unverändert)
function renderHistory(historyData) { function renderHistory(historyData) {
if (!dom.historyTableBody || !historyEnabled) return; if (!dom.historyTableBody || !historyEnabled) return;
dom.historyTableBody.innerHTML = ''; dom.historyTableBody.innerHTML = '';
@@ -477,47 +445,32 @@ document.addEventListener('DOMContentLoaded', () => {
dom.historyTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">Keine Einträge im Verlauf.</td></tr>'; return; dom.historyTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">Keine Einträge im Verlauf.</td></tr>'; return;
} }
historyData.forEach(entry => { historyData.forEach(entry => {
try { const row = dom.historyTableBody.insertRow();
const row = dom.historyTableBody.insertRow(); row.insertCell().textContent = entry.timestamp || '';
row.insertCell().textContent = entry.timestamp || ''; const platformCell = row.insertCell(); platformCell.classList.add('text-center');
const platformCell = row.insertCell(); platformCell.classList.add('text-center'); let platformIcon = '<i class="fas fa-question-circle text-muted" title="Unbekannt"></i>';
let platformIcon = '<i class="fas fa-question-circle text-muted" title="Unbekannt"></i>'; const platform = entry.platform || 'Unbekannt';
const platform = entry.platform || 'Unbekannt'; if (platform === 'SoundCloud') platformIcon = '<i class="fab fa-soundcloud text-warning" title="SoundCloud"></i>';
if (platform === 'SoundCloud') platformIcon = '<i class="fab fa-soundcloud text-warning" title="SoundCloud"></i>'; else if (platform === 'YouTube') platformIcon = '<i class="fab fa-youtube text-danger" title="YouTube"></i>';
else if (platform === 'YouTube') platformIcon = '<i class="fab fa-youtube text-danger" title="YouTube"></i>'; else if (platform === 'TikTok') platformIcon = '<i class="fab fa-tiktok" title="TikTok"></i>';
else if (platform === 'TikTok') platformIcon = '<i class="fab fa-tiktok" title="TikTok"></i>'; else if (platform === 'Instagram') platformIcon = '<i class="fab fa-instagram" title="Instagram Reel"></i>';
else if (platform === 'Instagram') platformIcon = '<i class="fab fa-instagram" title="Instagram Reel"></i>'; else if (platform === 'Twitter') platformIcon = '<i class="fab fa-x-twitter" title="Twitter/X"></i>';
else if (platform === 'Twitter') platformIcon = '<i class="fab fa-x-twitter" title="Twitter/X"></i>'; platformCell.innerHTML = platformIcon;
platformCell.innerHTML = platformIcon; const titleCell = row.insertCell(); titleCell.textContent = entry.title || 'N/A'; titleCell.title = entry.title || '';
const titleCell = row.insertCell(); titleCell.textContent = entry.title || 'N/A'; titleCell.title = entry.title || ''; createLinkCell(row.insertCell(), entry['source_url']);
createLinkCell(row.insertCell(), entry['source_url']); createLinkCell(row.insertCell(), entry['s3_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';
}
}); });
} }
// (Unverändert)
function createLinkCell(cell, url) { function createLinkCell(cell, url) {
if (!cell || typeof cell.appendChild !== 'function') return; if (!cell || !url) { cell.textContent = url || 'N/A'; return; }
cell.innerHTML = ''; const link = document.createElement('a'); link.href = url;
const urlString = (url === null || typeof url === 'undefined') ? '' : String(url).trim(); link.textContent = url.length > 50 ? url.substring(0, 47) + '...' : url;
if (urlString && (urlString.startsWith('http://') || urlString.startsWith('https://'))) { link.title = url; link.target = "_blank"; link.rel = "noopener noreferrer";
try { link.dataset.url = url;
const link = document.createElement('a'); link.href = urlString; cell.appendChild(link);
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; }
} }
// --- Kontextmenü --- // --- Kontextmenü (unverändert) ---
// (Unverändert)
let currentContextMenuUrl = null; let currentContextMenuUrl = null;
function showContextMenu(event, url) { function showContextMenu(event, url) {
event.preventDefault(); event.preventDefault();
@@ -540,8 +493,7 @@ document.addEventListener('DOMContentLoaded', () => {
hideContextMenu(); hideContextMenu();
} }
// --- Statistik --- // --- Statistik (unverändert) ---
// (Unverändert)
async function fetchStats() { async function fetchStats() {
if (!dom.statsTotalJobs || !dom.statsAvgDuration || !dom.statsTotalSize) return; if (!dom.statsTotalJobs || !dom.statsAvgDuration || !dom.statsTotalSize) return;
try { try {
@@ -553,20 +505,11 @@ document.addEventListener('DOMContentLoaded', () => {
dom.statsTotalSize.textContent = stats.total_size_formatted ?? 'N/A'; dom.statsTotalSize.textContent = stats.total_size_formatted ?? 'N/A';
} catch (error) { } catch (error) {
console.error('Fehler Laden Statistiken:', error); console.error('Fehler Laden Statistiken:', error);
dom.statsTotalJobs.textContent = 'Fehler'; dom.statsAvgDuration.textContent = 'Fehler'; dom.statsTotalSize.textContent = 'Fehler';
} }
} }
// --- Hilfsfunktionen --- // --- Hilfsfunktionen (unverändert) ---
// (Unverändert)
function copyToClipboard(text) { 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")) navigator.clipboard.writeText(text).then(() => appendLog("Link kopiert.", "info"))
.catch(err => { console.error('Async Copy failed:', err); alert("Kopieren fehlgeschlagen."); }); .catch(err => { console.error('Async Copy failed:', err); alert("Kopieren fehlgeschlagen."); });
} }
+74
View File
@@ -166,3 +166,77 @@ body {
#urlHelp { #urlHelp {
font-size: 0.8rem; 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 */
}
+14 -1
View File
@@ -1,4 +1,3 @@
<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -247,6 +246,20 @@
</div> </div>
</footer> </footer>
<!-- Fullscreen Processing Overlay -->
<div id="processing-overlay">
<img src="https://media.giphy.com/media/YQitE4YNQNahy/giphy.gif" alt="Processing Animation" width="300">
<p id="overlay-message">Hacking the mainframe...</p>
<!-- NEU: Status-Container im Overlay -->
<div class="overlay-status-container">
<p id="overlay-status-text">Initialisiere...</p>
<div class="progress" role="progressbar" aria-label="Overlay Progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div id="overlay-progress-bar" class="progress-bar" style="width: 0%">0%</div>
</div>
</div>
</div>
<!-- Bootstrap JS Bundle --> <!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<!-- Custom JS --> <!-- Custom JS -->