/** * BreakPilot Studio - File Manager Module * * Datei-Verwaltung für den Arbeitsblatt-Editor: * - Laden und Rendern der Dateiliste * - Upload von Dateien * - Löschen von Dateien * - Vorschau-Funktionen * - Navigation zwischen Dateien * * Refactored: 2026-01-19 */ import { t } from './i18n.js'; import { setStatus, setStatusWorking, setStatusError, setStatusSuccess, fetchJSON } from './api-helpers.js'; import { openLightbox } from './lightbox.js'; // State let allEingangFiles = []; let eingangFiles = []; let currentIndex = 0; let currentSelectedFile = null; let worksheetPairs = {}; let allWorksheetPairs = {}; let showOnlyUnitFiles = false; let currentUnitId = null; // DOM References (werden bei init gesetzt) let eingangListEl = null; let eingangCountEl = null; let previewContainer = null; let fileInput = null; let btnUploadInline = null; /** * Initialisiert den File Manager * @param {Object} options - Konfiguration */ export function initFileManager(options = {}) { eingangListEl = document.getElementById('eingang-list') || options.listEl; eingangCountEl = document.getElementById('eingang-count') || options.countEl; previewContainer = document.getElementById('preview-container') || options.previewEl; fileInput = document.getElementById('file-input') || options.fileInput; btnUploadInline = document.getElementById('btn-upload-inline') || options.uploadBtn; // Upload-Button Event if (btnUploadInline) { btnUploadInline.addEventListener('click', handleUpload); } // Initial load loadEingangFiles(); loadWorksheetPairs(); } /** * Setzt die aktuelle Lerneinheit * @param {string} unitId - Die Unit-ID */ export function setCurrentUnit(unitId) { currentUnitId = unitId; } /** * Setzt den Filter für Lerneinheit-Dateien * @param {boolean} show - Nur Unit-Dateien anzeigen */ export function setShowOnlyUnitFiles(show) { showOnlyUnitFiles = show; } /** * Gibt die aktuelle Dateiliste zurück * @returns {string[]} - Liste der Dateinamen */ export function getFiles() { return eingangFiles.slice(); } /** * Gibt den aktuellen Index zurück * @returns {number} */ export function getCurrentIndex() { return currentIndex; } /** * Setzt den aktuellen Index * @param {number} idx */ export function setCurrentIndex(idx) { currentIndex = idx; renderEingangList(); renderPreviewForCurrent(); } /** * Gibt den aktuell ausgewählten Dateinamen zurück * @returns {string|null} */ export function getCurrentFile() { return eingangFiles[currentIndex] || null; } /** * Lädt die Dateien aus dem Eingang */ export async function loadEingangFiles() { try { const data = await fetchJSON('/api/eingang-dateien'); allEingangFiles = data.eingang || []; eingangFiles = allEingangFiles.slice(); currentIndex = 0; renderEingangList(); } catch (e) { console.error('Fehler beim Laden der Dateien:', e); setStatusError(t('error') || 'Fehler', String(e)); } } /** * Lädt die Worksheet-Pairs (Original → Bereinigt) */ export async function loadWorksheetPairs() { try { const data = await fetchJSON('/api/worksheet-pairs'); allWorksheetPairs = {}; (data.pairs || []).forEach((p) => { allWorksheetPairs[p.original] = { clean_html: p.clean_html, clean_image: p.clean_image }; }); worksheetPairs = { ...allWorksheetPairs }; renderPreviewForCurrent(); } catch (e) { console.error('Fehler beim Laden der Neuaufbau-Daten:', e); setStatusError(t('error') || 'Fehler', String(e)); } } /** * Rendert die Dateiliste */ export function renderEingangList() { if (!eingangListEl) return; eingangListEl.innerHTML = ''; if (!eingangFiles.length) { const li = document.createElement('li'); li.className = 'file-empty'; li.textContent = t('no_files') || 'Noch keine Dateien vorhanden.'; eingangListEl.appendChild(li); if (eingangCountEl) { eingangCountEl.textContent = '0 ' + (t('files') || 'Dateien'); } return; } eingangFiles.forEach((filename, idx) => { const li = document.createElement('li'); li.className = 'file-item'; if (idx === currentIndex) { li.classList.add('active'); } const nameSpan = document.createElement('span'); nameSpan.className = 'file-item-name'; nameSpan.textContent = filename; const actionsSpan = document.createElement('span'); actionsSpan.style.display = 'flex'; actionsSpan.style.gap = '6px'; // Button: Aus Lerneinheit entfernen const removeFromUnitBtn = document.createElement('span'); removeFromUnitBtn.className = 'file-item-delete'; removeFromUnitBtn.textContent = '✕'; removeFromUnitBtn.title = t('remove_from_unit') || 'Aus Lerneinheit entfernen'; removeFromUnitBtn.addEventListener('click', (ev) => { ev.stopPropagation(); if (!currentUnitId) { alert(t('select_unit_first') || 'Zum Entfernen bitte zuerst eine Lerneinheit auswählen.'); return; } const ok = confirm(t('confirm_remove_from_unit') || 'Dieses Arbeitsblatt aus der aktuellen Lerneinheit entfernen? Die Datei selbst bleibt erhalten.'); if (!ok) return; removeWorksheetFromCurrentUnit(eingangFiles[idx]); }); // Button: Datei komplett löschen const deleteFileBtn = document.createElement('span'); deleteFileBtn.className = 'file-item-delete'; deleteFileBtn.textContent = '🗑️'; deleteFileBtn.title = t('delete_file') || 'Datei komplett löschen'; deleteFileBtn.style.color = '#ef4444'; deleteFileBtn.addEventListener('click', async (ev) => { ev.stopPropagation(); const ok = confirm(t('confirm_delete_file') || `Datei "${eingangFiles[idx]}" wirklich komplett löschen? Diese Aktion kann nicht rückgängig gemacht werden.`); if (!ok) return; await deleteFileCompletely(eingangFiles[idx]); }); actionsSpan.appendChild(removeFromUnitBtn); actionsSpan.appendChild(deleteFileBtn); li.appendChild(nameSpan); li.appendChild(actionsSpan); li.addEventListener('click', () => { currentIndex = idx; currentSelectedFile = filename; renderEingangList(); renderPreviewForCurrent(); // Event für andere Module window.dispatchEvent(new CustomEvent('fileSelected', { detail: { filename, index: idx } })); }); eingangListEl.appendChild(li); }); if (eingangCountEl) { eingangCountEl.textContent = eingangFiles.length + ' ' + (eingangFiles.length === 1 ? (t('file') || 'Datei') : (t('files') || 'Dateien')); } } /** * Rendert die Vorschau für die aktuelle Datei */ export function renderPreviewForCurrent() { if (!previewContainer) return; if (!eingangFiles.length) { const message = showOnlyUnitFiles && currentUnitId ? (t('no_files_in_unit') || 'Dieser Lerneinheit sind noch keine Arbeitsblätter zugeordnet.') : (t('no_files') || 'Keine Dateien vorhanden.'); previewContainer.innerHTML = `
${message}
`; return; } if (currentIndex < 0) currentIndex = 0; if (currentIndex >= eingangFiles.length) currentIndex = eingangFiles.length - 1; const filename = eingangFiles[currentIndex]; const entry = worksheetPairs[filename] || { clean_html: null, clean_image: null }; renderPreview(entry, currentIndex); } /** * Rendert die Vorschau (Original vs. Bereinigt) * @param {Object} entry - Die Worksheet-Pair-Daten * @param {number} index - Der Index */ function renderPreview(entry, index) { if (!previewContainer) return; previewContainer.innerHTML = ''; const wrapper = document.createElement('div'); wrapper.className = 'compare-wrapper'; // Original-Sektion const originalSection = createPreviewSection( t('original_scan') || 'Original-Scan', t('old_left') || 'Alt (links)', () => { const img = document.createElement('img'); img.className = 'preview-img'; const imgSrc = '/preview-file/' + encodeURIComponent(eingangFiles[index]); img.src = imgSrc; img.alt = 'Original ' + eingangFiles[index]; img.addEventListener('dblclick', () => openLightbox(imgSrc, eingangFiles[index])); return img; } ); // Bereinigt-Sektion const cleanSection = createPreviewSection( t('rebuilt_worksheet') || 'Neu aufgebautes Arbeitsblatt', createPrintButton(), () => { if (entry.clean_image) { const imgClean = document.createElement('img'); imgClean.className = 'preview-img'; const cleanSrc = '/preview-clean-file/' + encodeURIComponent(entry.clean_image); imgClean.src = cleanSrc; imgClean.alt = 'Neu aufgebaut ' + eingangFiles[index]; imgClean.addEventListener('dblclick', () => openLightbox(cleanSrc, eingangFiles[index] + ' (neu)')); return imgClean; } else if (entry.clean_html) { const frame = document.createElement('iframe'); frame.className = 'clean-frame'; frame.src = '/api/clean-html/' + encodeURIComponent(entry.clean_html); frame.title = t('rebuilt_worksheet') || 'Neu aufgebautes Arbeitsblatt'; frame.addEventListener('dblclick', () => { window.open('/api/clean-html/' + encodeURIComponent(entry.clean_html), '_blank'); }); return frame; } else { const placeholder = document.createElement('div'); placeholder.className = 'preview-placeholder'; placeholder.textContent = t('no_rebuild_data') || 'Noch keine Neuaufbau-Daten vorhanden.'; return placeholder; } } ); // Thumbnails in der Mitte const thumbsColumn = document.createElement('div'); thumbsColumn.className = 'preview-thumbnails'; thumbsColumn.id = 'preview-thumbnails-middle'; renderThumbnailsInColumn(thumbsColumn); wrapper.appendChild(originalSection); wrapper.appendChild(thumbsColumn); wrapper.appendChild(cleanSection); // Navigation const navDiv = createNavigationButtons(); wrapper.appendChild(navDiv); previewContainer.appendChild(wrapper); } /** * Erstellt eine Vorschau-Sektion */ function createPreviewSection(title, rightContent, contentFactory) { const section = document.createElement('div'); section.className = 'compare-section'; const header = document.createElement('div'); header.className = 'compare-header'; const titleSpan = document.createElement('span'); titleSpan.textContent = title; const rightSpan = document.createElement('span'); rightSpan.style.display = 'flex'; rightSpan.style.alignItems = 'center'; rightSpan.style.gap = '8px'; if (typeof rightContent === 'string') { rightSpan.textContent = rightContent; } else if (rightContent instanceof Node) { rightSpan.appendChild(rightContent); } header.appendChild(titleSpan); header.appendChild(rightSpan); const body = document.createElement('div'); body.className = 'compare-body'; const inner = document.createElement('div'); inner.className = 'compare-body-inner'; const content = contentFactory(); inner.appendChild(content); body.appendChild(inner); section.appendChild(header); section.appendChild(body); return section; } /** * Erstellt den Druck-Button */ function createPrintButton() { const container = document.createElement('span'); container.style.display = 'flex'; container.style.alignItems = 'center'; container.style.gap = '8px'; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-sm btn-ghost no-print'; btn.style.padding = '4px 10px'; btn.style.fontSize = '11px'; btn.textContent = '🖨️ ' + (t('print') || 'Drucken'); btn.addEventListener('click', () => { const currentFile = eingangFiles[currentIndex]; if (!currentFile) { alert(t('worksheet_no_data') || 'Keine Arbeitsblatt-Daten vorhanden.'); return; } window.open('/api/print-worksheet/' + encodeURIComponent(currentFile), '_blank'); }); const label = document.createElement('span'); label.textContent = t('new_right') || 'Neu (rechts)'; container.appendChild(btn); container.appendChild(label); return container; } /** * Rendert Thumbnails in einer Spalte */ function renderThumbnailsInColumn(container) { container.innerHTML = ''; if (eingangFiles.length <= 1) return; const maxThumbs = 5; let thumbCount = 0; for (let i = 0; i < eingangFiles.length && thumbCount < maxThumbs; i++) { if (i === currentIndex) continue; const filename = eingangFiles[i]; const thumb = document.createElement('div'); thumb.className = 'preview-thumb'; const img = document.createElement('img'); img.src = '/preview-file/' + encodeURIComponent(filename); img.alt = filename; const label = document.createElement('div'); label.className = 'preview-thumb-label'; label.textContent = `${i + 1}`; thumb.appendChild(img); thumb.appendChild(label); thumb.addEventListener('click', () => { currentIndex = i; renderEingangList(); renderPreviewForCurrent(); }); container.appendChild(thumb); thumbCount++; } } /** * Erstellt die Navigations-Buttons */ function createNavigationButtons() { const navDiv = document.createElement('div'); navDiv.className = 'preview-nav'; const prevBtn = document.createElement('button'); prevBtn.type = 'button'; prevBtn.textContent = '‹'; prevBtn.disabled = currentIndex === 0; prevBtn.addEventListener('click', () => { if (currentIndex > 0) { currentIndex--; renderEingangList(); renderPreviewForCurrent(); } }); const nextBtn = document.createElement('button'); nextBtn.type = 'button'; nextBtn.textContent = '›'; nextBtn.disabled = currentIndex >= eingangFiles.length - 1; nextBtn.addEventListener('click', () => { if (currentIndex < eingangFiles.length - 1) { currentIndex++; renderEingangList(); renderPreviewForCurrent(); } }); const positionSpan = document.createElement('span'); positionSpan.textContent = `${currentIndex + 1} ${t('of') || 'von'} ${eingangFiles.length}`; navDiv.appendChild(prevBtn); navDiv.appendChild(positionSpan); navDiv.appendChild(nextBtn); return navDiv; } /** * Handle File Upload */ async function handleUpload(ev) { ev.preventDefault(); ev.stopPropagation(); if (!fileInput) return; const files = fileInput.files; if (!files || !files.length) { alert(t('select_files_first') || 'Bitte erst Dateien auswählen.'); return; } const formData = new FormData(); for (const file of files) { formData.append('files', file); } try { setStatusWorking(t('uploading') || 'Upload läuft …'); const resp = await fetch('/api/upload-multi', { method: 'POST', body: formData, }); if (!resp.ok) { console.error('Upload-Fehler: HTTP', resp.status); setStatusError(t('upload_error') || 'Fehler beim Upload', 'HTTP ' + resp.status); return; } setStatusSuccess(t('upload_complete') || 'Upload abgeschlossen'); fileInput.value = ''; // Liste neu laden await loadEingangFiles(); await loadWorksheetPairs(); } catch (e) { console.error('Netzwerkfehler beim Upload', e); setStatusError(t('network_error') || 'Netzwerkfehler', String(e)); } } /** * Entfernt ein Arbeitsblatt aus der aktuellen Lerneinheit * @param {string} filename - Der Dateiname */ async function removeWorksheetFromCurrentUnit(filename) { if (!currentUnitId) return; try { setStatusWorking(t('removing_from_unit') || 'Entferne aus Lerneinheit...'); const resp = await fetch(`/api/units/${currentUnitId}/worksheets/${encodeURIComponent(filename)}`, { method: 'DELETE' }); if (!resp.ok) { throw new Error('HTTP ' + resp.status); } setStatusSuccess(t('removed_from_unit') || 'Aus Lerneinheit entfernt'); await loadEingangFiles(); } catch (e) { console.error('Fehler beim Entfernen:', e); setStatusError(t('error') || 'Fehler', String(e)); } } /** * Löscht eine Datei komplett * @param {string} filename - Der Dateiname */ async function deleteFileCompletely(filename) { try { setStatusWorking(t('deleting_file') || 'Lösche Datei...'); const resp = await fetch('/api/delete-file/' + encodeURIComponent(filename), { method: 'DELETE' }); if (!resp.ok) { throw new Error('HTTP ' + resp.status); } setStatusSuccess(t('file_deleted') || 'Datei gelöscht'); await loadEingangFiles(); await loadWorksheetPairs(); } catch (e) { console.error('Fehler beim Löschen:', e); setStatusError(t('error') || 'Fehler', String(e)); } } /** * Navigiert zur nächsten Datei */ export function nextFile() { if (currentIndex < eingangFiles.length - 1) { currentIndex++; renderEingangList(); renderPreviewForCurrent(); } } /** * Navigiert zur vorherigen Datei */ export function prevFile() { if (currentIndex > 0) { currentIndex--; renderEingangList(); renderPreviewForCurrent(); } } /** * Aktualisiert die Worksheet-Pairs für einen bestimmten Dateinamen * @param {string} filename * @param {Object} pair */ export function updateWorksheetPair(filename, pair) { worksheetPairs[filename] = pair; allWorksheetPairs[filename] = pair; if (eingangFiles[currentIndex] === filename) { renderPreviewForCurrent(); } }