/**
* 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();
}
}