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>
This commit is contained in:
234
backend/frontend/static/js/modules/lightbox.js
Normal file
234
backend/frontend/static/js/modules/lightbox.js
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* BreakPilot Studio - Lightbox Module
|
||||
*
|
||||
* Vollbild-Bildvorschau und Modal-Funktionen:
|
||||
* - Lightbox für Arbeitsblatt-Vorschauen
|
||||
* - Keyboard-Navigation (Escape zum Schließen)
|
||||
* - Click-outside zum Schließen
|
||||
*
|
||||
* Refactored: 2026-01-19
|
||||
*/
|
||||
|
||||
// DOM-Referenzen
|
||||
let lightboxEl = null;
|
||||
let lightboxImg = null;
|
||||
let lightboxCaption = null;
|
||||
let lightboxClose = null;
|
||||
|
||||
// Callback für Close-Event
|
||||
let onCloseCallback = null;
|
||||
|
||||
/**
|
||||
* Initialisiert die Lightbox
|
||||
* Sucht nach Standard-IDs oder erstellt das DOM
|
||||
*/
|
||||
export function initLightbox() {
|
||||
lightboxEl = document.getElementById('lightbox');
|
||||
lightboxImg = document.getElementById('lightbox-img');
|
||||
lightboxCaption = document.getElementById('lightbox-caption');
|
||||
lightboxClose = document.getElementById('lightbox-close');
|
||||
|
||||
// Falls keine Lightbox im DOM, erstelle sie
|
||||
if (!lightboxEl) {
|
||||
createLightboxDOM();
|
||||
}
|
||||
|
||||
// Event-Listener
|
||||
if (lightboxClose) {
|
||||
lightboxClose.addEventListener('click', closeLightbox);
|
||||
}
|
||||
|
||||
if (lightboxEl) {
|
||||
lightboxEl.addEventListener('click', (ev) => {
|
||||
// Schließen bei Klick auf Hintergrund
|
||||
if (ev.target === lightboxEl) {
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Escape-Taste
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
if (ev.key === 'Escape' && isLightboxOpen()) {
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt das Lightbox-DOM dynamisch
|
||||
*/
|
||||
function createLightboxDOM() {
|
||||
lightboxEl = document.createElement('div');
|
||||
lightboxEl.id = 'lightbox';
|
||||
lightboxEl.className = 'lightbox hidden';
|
||||
lightboxEl.innerHTML = `
|
||||
<div class="lightbox-content">
|
||||
<button class="lightbox-close" id="lightbox-close">Schließen ✕</button>
|
||||
<img id="lightbox-img" class="lightbox-img" src="" alt="Vorschau">
|
||||
<div id="lightbox-caption" class="lightbox-caption"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(lightboxEl);
|
||||
|
||||
// Referenzen aktualisieren
|
||||
lightboxImg = document.getElementById('lightbox-img');
|
||||
lightboxCaption = document.getElementById('lightbox-caption');
|
||||
lightboxClose = document.getElementById('lightbox-close');
|
||||
|
||||
// CSS injizieren falls nicht vorhanden
|
||||
if (!document.getElementById('lightbox-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'lightbox-styles';
|
||||
style.textContent = `
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.lightbox.hidden {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.lightbox-content {
|
||||
position: relative;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
.lightbox-img {
|
||||
max-width: 100%;
|
||||
max-height: 85vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.lightbox-close {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.lightbox-close:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.lightbox-caption {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet die Lightbox mit einem Bild
|
||||
* @param {string} src - Bild-URL
|
||||
* @param {string} [caption] - Optionale Bildunterschrift
|
||||
*/
|
||||
export function openLightbox(src, caption = '') {
|
||||
if (!src) {
|
||||
console.warn('openLightbox: No image source provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy-Init falls noch nicht initialisiert
|
||||
if (!lightboxEl) {
|
||||
initLightbox();
|
||||
}
|
||||
|
||||
if (lightboxImg) {
|
||||
lightboxImg.src = src;
|
||||
lightboxImg.alt = caption || 'Vorschau';
|
||||
}
|
||||
|
||||
if (lightboxCaption) {
|
||||
lightboxCaption.textContent = caption;
|
||||
}
|
||||
|
||||
if (lightboxEl) {
|
||||
lightboxEl.classList.remove('hidden');
|
||||
// Body-Scroll verhindern
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schließt die Lightbox
|
||||
*/
|
||||
export function closeLightbox() {
|
||||
if (lightboxEl) {
|
||||
lightboxEl.classList.add('hidden');
|
||||
// Body-Scroll wiederherstellen
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
if (lightboxImg) {
|
||||
lightboxImg.src = '';
|
||||
}
|
||||
|
||||
// Callback ausführen
|
||||
if (onCloseCallback) {
|
||||
onCloseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Lightbox geöffnet ist
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLightboxOpen() {
|
||||
return lightboxEl && !lightboxEl.classList.contains('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt einen Callback für das Close-Event
|
||||
* @param {function} callback
|
||||
*/
|
||||
export function onClose(callback) {
|
||||
onCloseCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wechselt das Bild in der offenen Lightbox
|
||||
* @param {string} src - Neue Bild-URL
|
||||
* @param {string} [caption] - Neue Bildunterschrift
|
||||
*/
|
||||
export function changeLightboxImage(src, caption = '') {
|
||||
if (lightboxImg) {
|
||||
lightboxImg.src = src;
|
||||
lightboxImg.alt = caption || 'Vorschau';
|
||||
}
|
||||
|
||||
if (lightboxCaption) {
|
||||
lightboxCaption.textContent = caption;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Text des Close-Buttons
|
||||
* @param {string} text - Der neue Text
|
||||
*/
|
||||
export function setCloseButtonText(text) {
|
||||
if (lightboxClose) {
|
||||
lightboxClose.textContent = text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user