This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/static/js/modules/file-manager.js
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

615 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 = `<div class="preview-placeholder">${message}</div>`;
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();
}
}