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>
1919 lines
55 KiB
Python
1919 lines
55 KiB
Python
"""
|
|
BreakPilot Studio - Lerneinheiten/Arbeitsblaetter Modul
|
|
|
|
Refactored: 2024-12-18
|
|
- Kachel-basierte Startansicht
|
|
- Sub-Module fuer verschiedene Funktionen
|
|
|
|
Funktionen (als Kacheln):
|
|
- Lerneinheiten erstellen und verwalten
|
|
- Dateien hochladen
|
|
- Dateien bereinigen (OCR, Neuaufbau)
|
|
- Lernflows (Interaktive Uebungen)
|
|
- Multiple Choice Tests
|
|
- Lueckentexte
|
|
- Mindmap Generator
|
|
- Uebersetzungen
|
|
"""
|
|
|
|
|
|
class WorksheetsModule:
|
|
"""Modul fuer Lerneinheiten und Arbeitsblaetter."""
|
|
|
|
@staticmethod
|
|
def get_css() -> str:
|
|
"""CSS fuer das Worksheets-Modul."""
|
|
return """
|
|
/* =============================================
|
|
WORKSHEETS MODULE - Lerneinheiten & Arbeitsblaetter
|
|
============================================= */
|
|
|
|
/* Panel Layout */
|
|
.panel-worksheets {
|
|
display: none;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: var(--bp-bg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-worksheets.active {
|
|
display: flex;
|
|
}
|
|
|
|
/* Worksheets Header */
|
|
.worksheets-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px 32px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.worksheets-title-section h1 {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.worksheets-subtitle {
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.worksheets-nav {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.worksheets-nav-btn {
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
color: var(--bp-text-muted);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.worksheets-nav-btn:hover {
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.worksheets-nav-btn.active {
|
|
background: var(--bp-primary);
|
|
border-color: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
/* Worksheets Content - Kacheln */
|
|
.worksheets-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 32px;
|
|
}
|
|
|
|
/* Tiles Grid */
|
|
.worksheets-tiles {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 24px;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Section Header */
|
|
.tiles-section {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.tiles-section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.tiles-section-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.tiles-section-icon.create { background: rgba(59, 130, 246, 0.15); }
|
|
.tiles-section-icon.process { background: rgba(16, 185, 129, 0.15); }
|
|
.tiles-section-icon.generate { background: rgba(245, 158, 11, 0.15); }
|
|
.tiles-section-icon.translate { background: rgba(139, 92, 246, 0.15); }
|
|
|
|
.tiles-section-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.tiles-section-desc {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Worksheet Tile */
|
|
.worksheet-tile {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.worksheet-tile:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.worksheet-tile::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: var(--bp-primary);
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.worksheet-tile:hover::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.tile-icon-container {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 28px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* Icon Colors */
|
|
.tile-icon-container.blue { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); }
|
|
.tile-icon-container.green { background: linear-gradient(135deg, #10b981 0%, #059669 100%); }
|
|
.tile-icon-container.orange { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); }
|
|
.tile-icon-container.purple { background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%); }
|
|
.tile-icon-container.red { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); }
|
|
.tile-icon-container.cyan { background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); }
|
|
.tile-icon-container.pink { background: linear-gradient(135deg, #ec4899 0%, #be185d 100%); }
|
|
.tile-icon-container.teal { background: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); }
|
|
|
|
.tile-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.tile-description {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
line-height: 1.5;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.tile-features {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
.tile-feature-tag {
|
|
padding: 4px 10px;
|
|
background: var(--bp-bg);
|
|
border-radius: 12px;
|
|
font-size: 11px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.tile-arrow {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
background: var(--bp-bg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--bp-text-muted);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.worksheet-tile:hover .tile-arrow {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
/* Sub-Panel (hidden by default, shown when tile clicked) */
|
|
.worksheets-subpanel {
|
|
display: none;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
.worksheets-subpanel.active {
|
|
display: flex;
|
|
}
|
|
|
|
.subpanel-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.subpanel-back {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
background: var(--bp-bg);
|
|
color: var(--bp-text);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.subpanel-back:hover {
|
|
background: var(--bp-surface-elevated);
|
|
}
|
|
|
|
.subpanel-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.subpanel-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 24px;
|
|
}
|
|
|
|
/* Status Bar */
|
|
.worksheets-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 24px;
|
|
border-top: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #10b981;
|
|
}
|
|
|
|
.status-indicator.busy {
|
|
background: #f59e0b;
|
|
animation: statusPulse 1.5s infinite;
|
|
}
|
|
|
|
.status-indicator.error {
|
|
background: #ef4444;
|
|
}
|
|
|
|
@keyframes statusPulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.status-text {
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.status-detail {
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* ============================================
|
|
SUB-MODULE STYLES: Lerneinheiten Manager
|
|
============================================ */
|
|
|
|
.units-manager {
|
|
display: grid;
|
|
grid-template-columns: 320px 1fr;
|
|
gap: 24px;
|
|
height: 100%;
|
|
}
|
|
|
|
.units-sidebar {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.units-form {
|
|
padding: 16px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.units-form h3 {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.units-form input {
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
margin-bottom: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 8px;
|
|
background: var(--bp-bg);
|
|
color: var(--bp-text);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.units-form input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.units-form .form-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.units-form .form-row input {
|
|
flex: 1;
|
|
}
|
|
|
|
.units-list {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
}
|
|
|
|
.unit-list-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.unit-list-item:hover {
|
|
background: var(--bp-bg);
|
|
}
|
|
|
|
.unit-list-item.selected {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
.unit-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.unit-name {
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.unit-meta {
|
|
font-size: 11px;
|
|
opacity: 0.7;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.unit-delete {
|
|
opacity: 0;
|
|
cursor: pointer;
|
|
padding: 4px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.unit-list-item:hover .unit-delete {
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.unit-list-item:hover .unit-delete:hover {
|
|
opacity: 1;
|
|
color: #ef4444;
|
|
}
|
|
|
|
.units-main {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.units-empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 300px;
|
|
text-align: center;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.units-empty-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* ============================================
|
|
SUB-MODULE STYLES: File Upload
|
|
============================================ */
|
|
|
|
.upload-container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.upload-drop-zone {
|
|
border: 2px dashed var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 48px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.upload-drop-zone:hover,
|
|
.upload-drop-zone.dragover {
|
|
border-color: var(--bp-primary);
|
|
background: rgba(59, 130, 246, 0.05);
|
|
}
|
|
|
|
.upload-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.upload-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.upload-hint {
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.upload-formats {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
padding: 8px 16px;
|
|
background: var(--bp-bg);
|
|
border-radius: 20px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.upload-progress {
|
|
margin-top: 24px;
|
|
padding: 16px;
|
|
background: var(--bp-bg);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.upload-file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.upload-file-name {
|
|
flex: 1;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.upload-file-status {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* ============================================
|
|
SUB-MODULE STYLES: Generator Tiles
|
|
============================================ */
|
|
|
|
.generator-options {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.generator-option {
|
|
padding: 20px;
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-align: center;
|
|
}
|
|
|
|
.generator-option:hover {
|
|
border-color: var(--bp-primary);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.generator-option.selected {
|
|
border-color: var(--bp-primary);
|
|
background: rgba(59, 130, 246, 0.05);
|
|
}
|
|
|
|
.generator-option-icon {
|
|
font-size: 32px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.generator-option-title {
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.generator-option-desc {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* ============================================
|
|
Legacy Support: Original 3-Column Layout
|
|
============================================ */
|
|
|
|
/* Linke Spalte - Lerneinheiten */
|
|
.worksheets-units-panel {
|
|
width: 280px;
|
|
border-right: 1px solid var(--bp-border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.units-header {
|
|
padding: 16px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.units-header h3 {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text-muted);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
/* Mittlere Spalte - Dateiliste */
|
|
.worksheets-files-panel {
|
|
width: 260px;
|
|
border-right: 1px solid var(--bp-border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bp-bg);
|
|
}
|
|
|
|
.files-header {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.files-list {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
}
|
|
|
|
.file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 12px;
|
|
margin-bottom: 2px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.file-item:hover {
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.file-item.active {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
/* Rechte Spalte - Preview */
|
|
.worksheets-preview-panel {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Compare Wrapper */
|
|
.compare-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
gap: 16px;
|
|
padding: 16px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.compare-section {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.compare-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px 16px;
|
|
background: var(--bp-bg);
|
|
border-bottom: 1px solid var(--bp-border);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.compare-body {
|
|
flex: 1;
|
|
overflow: auto;
|
|
padding: 16px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.preview-img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
border-radius: 4px;
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
/* Lightbox */
|
|
.lightbox {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 10000;
|
|
padding: 20px;
|
|
}
|
|
|
|
.lightbox.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.lightbox-close {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
font-size: 32px;
|
|
color: white;
|
|
cursor: pointer;
|
|
opacity: 0.7;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.lightbox-close:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
.lightbox-img {
|
|
max-width: 95%;
|
|
max-height: 85%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.lightbox-caption {
|
|
margin-top: 16px;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
font-size: 14px;
|
|
}
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_html() -> str:
|
|
"""HTML fuer das Worksheets-Modul mit Kachel-basierter Startansicht."""
|
|
return """
|
|
<!-- Worksheets Panel -->
|
|
<div id="panel-worksheets" class="panel-worksheets">
|
|
|
|
<!-- Main Tiles View -->
|
|
<div id="worksheets-tiles-view">
|
|
<!-- Header -->
|
|
<div class="worksheets-header">
|
|
<div class="worksheets-title-section">
|
|
<h1>Arbeitsblaetter Studio</h1>
|
|
<p class="worksheets-subtitle">Erstelle und verwalte Lernmaterialien fuer deine Schueler</p>
|
|
</div>
|
|
<div class="worksheets-nav">
|
|
<button class="worksheets-nav-btn active" onclick="showWorksheetsTiles()">Uebersicht</button>
|
|
<button class="worksheets-nav-btn" onclick="showWorksheetsManager()">Lerneinheiten</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tiles Content -->
|
|
<div class="worksheets-content">
|
|
<!-- Erstellen & Verwalten -->
|
|
<div class="tiles-section">
|
|
<div class="tiles-section-header">
|
|
<div class="tiles-section-icon create">📝</div>
|
|
<div>
|
|
<div class="tiles-section-title">Erstellen & Verwalten</div>
|
|
<div class="tiles-section-desc">Lerneinheiten anlegen und Materialien hochladen</div>
|
|
</div>
|
|
</div>
|
|
<div class="worksheets-tiles">
|
|
<!-- Lerneinheiten -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('units')">
|
|
<div class="tile-icon-container blue">📚</div>
|
|
<div class="tile-title">Lerneinheiten</div>
|
|
<div class="tile-description">Erstelle und verwalte Lerneinheiten fuer deine Schueler. Ordne Materialien zu und verfolge den Fortschritt.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">CRUD</span>
|
|
<span class="tile-feature-tag">Organisation</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
|
|
<!-- Dateien hochladen -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('upload')">
|
|
<div class="tile-icon-container green">📤</div>
|
|
<div class="tile-title">Dateien hochladen</div>
|
|
<div class="tile-description">Lade Arbeitsblaetter, Bilder und PDFs hoch. Unterstuetzt Drag & Drop und Batch-Upload.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">PDF</span>
|
|
<span class="tile-feature-tag">Bilder</span>
|
|
<span class="tile-feature-tag">Batch</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Verarbeiten -->
|
|
<div class="tiles-section">
|
|
<div class="tiles-section-header">
|
|
<div class="tiles-section-icon process">⚙</div>
|
|
<div>
|
|
<div class="tiles-section-title">Verarbeiten</div>
|
|
<div class="tiles-section-desc">Automatische Aufbereitung und Bereinigung</div>
|
|
</div>
|
|
</div>
|
|
<div class="worksheets-tiles">
|
|
<!-- Dateien bereinigen -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('clean')">
|
|
<div class="tile-icon-container cyan">🔧</div>
|
|
<div class="tile-title">Dateien bereinigen</div>
|
|
<div class="tile-description">OCR-Verarbeitung und automatischer Neuaufbau von gescannten Arbeitsblaettern.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">OCR</span>
|
|
<span class="tile-feature-tag">Neuaufbau</span>
|
|
<span class="tile-feature-tag">AI</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
|
|
<!-- Lernflows -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('lernflows')">
|
|
<div class="tile-icon-container orange">🎯</div>
|
|
<div class="tile-title">Lernflows</div>
|
|
<div class="tile-description">Interaktive Lernpfade erstellen. Kombiniere verschiedene Aufgabentypen zu einem Flow.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">Interaktiv</span>
|
|
<span class="tile-feature-tag">Sequenzen</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generieren -->
|
|
<div class="tiles-section">
|
|
<div class="tiles-section-header">
|
|
<div class="tiles-section-icon generate">✨</div>
|
|
<div>
|
|
<div class="tiles-section-title">Druckdateien generieren</div>
|
|
<div class="tiles-section-desc">AI-gestuetzte Erstellung von Uebungsmaterialien</div>
|
|
</div>
|
|
</div>
|
|
<div class="worksheets-tiles">
|
|
<!-- Multiple Choice -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('mc')">
|
|
<div class="tile-icon-container purple">✅</div>
|
|
<div class="tile-title">Multiple Choice</div>
|
|
<div class="tile-description">Generiere Multiple-Choice-Tests aus deinen Lernmaterialien. Inkl. Antwortschluessel.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">Tests</span>
|
|
<span class="tile-feature-tag">AI</span>
|
|
<span class="tile-feature-tag">Druckbar</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
|
|
<!-- Lueckentexte -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('cloze')">
|
|
<div class="tile-icon-container red">📝</div>
|
|
<div class="tile-title">Lueckentexte</div>
|
|
<div class="tile-description">Erstelle Lueckentexte fuer effektives Vokabel- und Faktenlernen.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">Uebung</span>
|
|
<span class="tile-feature-tag">AI</span>
|
|
<span class="tile-feature-tag">Druckbar</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
|
|
<!-- Mindmap -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('mindmap')">
|
|
<div class="tile-icon-container teal">🗺</div>
|
|
<div class="tile-title">Mindmap</div>
|
|
<div class="tile-description">Visualisiere Zusammenhaenge in einer uebersichtlichen Mindmap.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">Visualisierung</span>
|
|
<span class="tile-feature-tag">AI</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
|
|
<!-- Fragen & Antworten -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('qa')">
|
|
<div class="tile-icon-container pink">❓</div>
|
|
<div class="tile-title">Fragen & Antworten</div>
|
|
<div class="tile-description">Generiere Fragenkataloge und Lernkarten aus deinen Materialien.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">Lernkarten</span>
|
|
<span class="tile-feature-tag">AI</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Uebersetzen -->
|
|
<div class="tiles-section">
|
|
<div class="tiles-section-header">
|
|
<div class="tiles-section-icon translate">🌐</div>
|
|
<div>
|
|
<div class="tiles-section-title">Uebersetzen</div>
|
|
<div class="tiles-section-desc">Mehrsprachige Unterstuetzung fuer alle Materialien</div>
|
|
</div>
|
|
</div>
|
|
<div class="worksheets-tiles">
|
|
<!-- Uebersetzungen -->
|
|
<div class="worksheet-tile" onclick="openWorksheetsSubpanel('translate')">
|
|
<div class="tile-icon-container purple">🗣</div>
|
|
<div class="tile-title">Uebersetzungen</div>
|
|
<div class="tile-description">Uebersetze Arbeitsblaetter und generierte Inhalte in verschiedene Sprachen.</div>
|
|
<div class="tile-features">
|
|
<span class="tile-feature-tag">DE</span>
|
|
<span class="tile-feature-tag">EN</span>
|
|
<span class="tile-feature-tag">TR</span>
|
|
<span class="tile-feature-tag">AR</span>
|
|
<span class="tile-feature-tag">+10</span>
|
|
</div>
|
|
<div class="tile-arrow">→</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Lerneinheiten Manager -->
|
|
<div id="worksheets-subpanel-units" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Lerneinheiten verwalten</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<div class="units-manager">
|
|
<div class="units-sidebar">
|
|
<div class="units-form">
|
|
<h3>Neue Lerneinheit</h3>
|
|
<input type="text" id="new-unit-student" placeholder="Schueler/in">
|
|
<div class="form-row">
|
|
<input type="text" id="new-unit-subject" placeholder="Fach">
|
|
<input type="text" id="new-unit-grade" placeholder="Klasse">
|
|
</div>
|
|
<input type="text" id="new-unit-title" placeholder="Thema / Titel">
|
|
<button class="btn btn-primary" onclick="createNewUnit()" style="width: 100%; margin-top: 8px;">Anlegen</button>
|
|
</div>
|
|
<div class="units-list" id="units-list-container">
|
|
<!-- Dynamisch gefuellt -->
|
|
</div>
|
|
</div>
|
|
<div class="units-main" id="units-detail-container">
|
|
<div class="units-empty">
|
|
<div class="units-empty-icon">📚</div>
|
|
<h3>Lerneinheit auswaehlen</h3>
|
|
<p>Waehle eine Lerneinheit aus der Liste oder erstelle eine neue.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Dateien hochladen -->
|
|
<div id="worksheets-subpanel-upload" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Dateien hochladen</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<div class="upload-container">
|
|
<div class="upload-drop-zone" id="worksheets-upload-zone">
|
|
<div class="upload-icon">📤</div>
|
|
<div class="upload-title">Dateien hochladen</div>
|
|
<div class="upload-hint">Dateien hierher ziehen oder klicken zum Auswaehlen</div>
|
|
<div class="upload-formats">PDF, JPG, PNG - max. 50MB pro Datei</div>
|
|
<input type="file" id="worksheets-file-input" multiple accept=".pdf,.jpg,.jpeg,.png" hidden>
|
|
</div>
|
|
<div class="upload-progress" id="upload-progress" style="display: none;">
|
|
<!-- Dynamisch gefuellt -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Dateien bereinigen -->
|
|
<div id="worksheets-subpanel-clean" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Dateien bereinigen</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Waehle Dateien aus einer Lerneinheit und starte den OCR-Neuaufbau.</p>
|
|
<div id="clean-files-list">
|
|
<!-- Wird dynamisch geladen -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: MC Generator -->
|
|
<div id="worksheets-subpanel-mc" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Multiple Choice Generator</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Generiere Multiple-Choice-Tests aus deinen Lernmaterialien.</p>
|
|
<div class="generator-options">
|
|
<div class="generator-option" onclick="selectGeneratorOption(this, 'easy')">
|
|
<div class="generator-option-icon">🌟</div>
|
|
<div class="generator-option-title">Einfach</div>
|
|
<div class="generator-option-desc">5 Fragen, grundlegend</div>
|
|
</div>
|
|
<div class="generator-option selected" onclick="selectGeneratorOption(this, 'medium')">
|
|
<div class="generator-option-icon">💪</div>
|
|
<div class="generator-option-title">Mittel</div>
|
|
<div class="generator-option-desc">10 Fragen, ausgewogen</div>
|
|
</div>
|
|
<div class="generator-option" onclick="selectGeneratorOption(this, 'hard')">
|
|
<div class="generator-option-icon">🔥</div>
|
|
<div class="generator-option-title">Schwer</div>
|
|
<div class="generator-option-desc">15 Fragen, anspruchsvoll</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary" style="margin-top: 24px;" onclick="generateMC()">✨ MC-Test generieren</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Lueckentexte -->
|
|
<div id="worksheets-subpanel-cloze" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Lueckentext Generator</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Erstelle Lueckentexte aus deinen Lernmaterialien.</p>
|
|
<button class="btn btn-primary" onclick="generateCloze()">✨ Lueckentext generieren</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Mindmap -->
|
|
<div id="worksheets-subpanel-mindmap" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Mindmap Generator</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Erstelle eine visuelle Mindmap aus deinen Lernmaterialien.</p>
|
|
<button class="btn btn-primary" onclick="generateMindmap()">✨ Mindmap generieren</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Q&A -->
|
|
<div id="worksheets-subpanel-qa" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Fragen & Antworten Generator</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Generiere Fragen und Antworten fuer Lernkarten.</p>
|
|
<button class="btn btn-primary" onclick="generateQA()">✨ Fragen generieren</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Lernflows -->
|
|
<div id="worksheets-subpanel-lernflows" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Lernflows erstellen</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Erstelle interaktive Lernpfade aus verschiedenen Aufgabentypen.</p>
|
|
<button class="btn btn-primary" onclick="createLernflow()">🎯 Neuen Lernflow erstellen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-Panel: Uebersetzen -->
|
|
<div id="worksheets-subpanel-translate" class="worksheets-subpanel">
|
|
<div class="subpanel-header">
|
|
<button class="subpanel-back" onclick="closeWorksheetsSubpanel()">←</button>
|
|
<div class="subpanel-title">Uebersetzungen</div>
|
|
</div>
|
|
<div class="subpanel-content">
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">Uebersetze deine Materialien in verschiedene Sprachen.</p>
|
|
<div class="generator-options">
|
|
<div class="generator-option" onclick="selectLanguage('en')">
|
|
<div class="generator-option-icon">🇬🇧</div>
|
|
<div class="generator-option-title">Englisch</div>
|
|
</div>
|
|
<div class="generator-option" onclick="selectLanguage('tr')">
|
|
<div class="generator-option-icon">🇹🇷</div>
|
|
<div class="generator-option-title">Tuerkisch</div>
|
|
</div>
|
|
<div class="generator-option" onclick="selectLanguage('ar')">
|
|
<div class="generator-option-icon">🇦🇪</div>
|
|
<div class="generator-option-title">Arabisch</div>
|
|
</div>
|
|
<div class="generator-option" onclick="selectLanguage('uk')">
|
|
<div class="generator-option-icon">🇺🇦</div>
|
|
<div class="generator-option-title">Ukrainisch</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Bar -->
|
|
<div class="worksheets-status">
|
|
<span class="status-indicator" id="ws-status-indicator"></span>
|
|
<span class="status-text" id="ws-status-text">Bereit</span>
|
|
<span class="status-detail" id="ws-status-detail"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lightbox -->
|
|
<div id="lightbox" class="lightbox hidden">
|
|
<span class="lightbox-close" id="lightbox-close">×</span>
|
|
<img id="lightbox-img" class="lightbox-img" src="" alt="">
|
|
<div id="lightbox-caption" class="lightbox-caption"></div>
|
|
</div>
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_js() -> str:
|
|
"""JavaScript fuer das Worksheets-Modul."""
|
|
return """
|
|
// =============================================
|
|
// WORKSHEETS MODULE - Refactored with Tiles
|
|
// =============================================
|
|
|
|
let worksheetsInitialized = false;
|
|
let worksheetsUnits = [];
|
|
let worksheetsCurrentUnit = null;
|
|
|
|
function loadWorksheetsModule() {
|
|
if (worksheetsInitialized) {
|
|
console.log('Worksheets module already initialized');
|
|
return;
|
|
}
|
|
|
|
console.log('Loading Worksheets Module (Tiles)...');
|
|
|
|
// Initialize upload zone
|
|
initWorksheetsUpload();
|
|
|
|
// Load units
|
|
loadWorksheetsUnits();
|
|
|
|
// Init lightbox
|
|
initWorksheetsLightbox();
|
|
|
|
worksheetsInitialized = true;
|
|
console.log('Worksheets Module loaded successfully');
|
|
}
|
|
|
|
// =============================================
|
|
// VIEW SWITCHING
|
|
// =============================================
|
|
|
|
function showWorksheetsTiles() {
|
|
document.getElementById('worksheets-tiles-view').style.display = 'block';
|
|
closeWorksheetsSubpanel();
|
|
|
|
document.querySelectorAll('.worksheets-nav-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelector('.worksheets-nav-btn').classList.add('active');
|
|
}
|
|
|
|
function showWorksheetsManager() {
|
|
openWorksheetsSubpanel('units');
|
|
|
|
document.querySelectorAll('.worksheets-nav-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.worksheets-nav-btn')[1].classList.add('active');
|
|
}
|
|
|
|
// =============================================
|
|
// SUBPANEL NAVIGATION
|
|
// =============================================
|
|
|
|
function openWorksheetsSubpanel(panelId) {
|
|
// Hide tiles view
|
|
document.getElementById('worksheets-tiles-view').style.display = 'none';
|
|
|
|
// Hide all subpanels
|
|
document.querySelectorAll('.worksheets-subpanel').forEach(p => {
|
|
p.classList.remove('active');
|
|
});
|
|
|
|
// Show selected subpanel
|
|
const panel = document.getElementById('worksheets-subpanel-' + panelId);
|
|
if (panel) {
|
|
panel.classList.add('active');
|
|
}
|
|
}
|
|
|
|
function closeWorksheetsSubpanel() {
|
|
document.querySelectorAll('.worksheets-subpanel').forEach(p => {
|
|
p.classList.remove('active');
|
|
});
|
|
document.getElementById('worksheets-tiles-view').style.display = 'block';
|
|
|
|
// Reset nav buttons
|
|
document.querySelectorAll('.worksheets-nav-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelector('.worksheets-nav-btn').classList.add('active');
|
|
}
|
|
|
|
// =============================================
|
|
// STATUS
|
|
// =============================================
|
|
|
|
function setWorksheetsStatus(text, detail = '', state = 'idle') {
|
|
const indicator = document.getElementById('ws-status-indicator');
|
|
const textEl = document.getElementById('ws-status-text');
|
|
const detailEl = document.getElementById('ws-status-detail');
|
|
|
|
if (textEl) textEl.textContent = text;
|
|
if (detailEl) detailEl.textContent = detail;
|
|
if (indicator) {
|
|
indicator.classList.remove('busy', 'error');
|
|
if (state === 'busy') indicator.classList.add('busy');
|
|
else if (state === 'error') indicator.classList.add('error');
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// LERNEINHEITEN
|
|
// =============================================
|
|
|
|
async function loadWorksheetsUnits() {
|
|
try {
|
|
const resp = await fetch('/api/learning-units/');
|
|
if (!resp.ok) {
|
|
console.error('Failed to load units');
|
|
return;
|
|
}
|
|
worksheetsUnits = await resp.json();
|
|
renderWorksheetsUnits();
|
|
} catch (e) {
|
|
console.error('Error loading units:', e);
|
|
}
|
|
}
|
|
|
|
function renderWorksheetsUnits() {
|
|
const container = document.getElementById('units-list-container');
|
|
if (!container) return;
|
|
|
|
if (!worksheetsUnits.length) {
|
|
container.innerHTML = '<div style="padding: 16px; text-align: center; color: var(--bp-text-muted);">Keine Lerneinheiten vorhanden</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = worksheetsUnits.map(unit => `
|
|
<div class="unit-list-item ${worksheetsCurrentUnit?.id === unit.id ? 'selected' : ''}"
|
|
onclick="selectWorksheetsUnit('${unit.id}')">
|
|
<div class="unit-info">
|
|
<div class="unit-name">${unit.label || unit.title || 'Lerneinheit'}</div>
|
|
<div class="unit-meta">${unit.subject || ''} ${unit.grade || ''} - ${(unit.worksheet_files || []).length} Dateien</div>
|
|
</div>
|
|
<span class="unit-delete" onclick="event.stopPropagation(); deleteWorksheetsUnit('${unit.id}')">🗑</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function selectWorksheetsUnit(unitId) {
|
|
worksheetsCurrentUnit = worksheetsUnits.find(u => u.id === unitId);
|
|
renderWorksheetsUnits();
|
|
showUnitDetail();
|
|
}
|
|
|
|
function showUnitDetail() {
|
|
const container = document.getElementById('units-detail-container');
|
|
if (!container || !worksheetsCurrentUnit) return;
|
|
|
|
const unit = worksheetsCurrentUnit;
|
|
const files = unit.worksheet_files || [];
|
|
|
|
container.innerHTML = `
|
|
<h2 style="margin-bottom: 8px;">${unit.label || unit.title}</h2>
|
|
<p style="color: var(--bp-text-muted); margin-bottom: 24px;">
|
|
${unit.student_name ? 'Schueler: ' + unit.student_name + ' | ' : ''}
|
|
${unit.subject || ''} ${unit.grade || ''}
|
|
</p>
|
|
|
|
<h3 style="font-size: 14px; color: var(--bp-text-muted); margin-bottom: 12px;">
|
|
Zugeordnete Dateien (${files.length})
|
|
</h3>
|
|
|
|
${files.length ? `
|
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
${files.map(f => `
|
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: var(--bp-bg); border-radius: 8px;">
|
|
<span>${f}</span>
|
|
<span style="cursor: pointer; color: var(--bp-text-muted);" onclick="removeFileFromUnit('${f}')">✕</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
` : '<p style="color: var(--bp-text-muted);">Keine Dateien zugeordnet</p>'}
|
|
|
|
<div style="margin-top: 24px; display: flex; gap: 12px;">
|
|
<button class="btn btn-ghost" onclick="openWorksheetsSubpanel('upload')">Dateien hinzufuegen</button>
|
|
<button class="btn btn-ghost" onclick="openWorksheetsSubpanel('mc')">MC-Test erstellen</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function createNewUnit() {
|
|
const student = document.getElementById('new-unit-student')?.value.trim() || '';
|
|
const subject = document.getElementById('new-unit-subject')?.value.trim() || '';
|
|
const grade = document.getElementById('new-unit-grade')?.value.trim() || '';
|
|
const title = document.getElementById('new-unit-title')?.value.trim() || '';
|
|
|
|
if (!title) {
|
|
alert('Bitte einen Titel eingeben');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setWorksheetsStatus('Erstelle Lerneinheit...', '', 'busy');
|
|
const resp = await fetch('/api/learning-units/', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
label: title,
|
|
student_name: student,
|
|
subject: subject,
|
|
grade: grade,
|
|
title: title
|
|
})
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
throw new Error('Failed to create unit');
|
|
}
|
|
|
|
const newUnit = await resp.json();
|
|
worksheetsUnits.push(newUnit);
|
|
worksheetsCurrentUnit = newUnit;
|
|
|
|
// Clear form
|
|
['new-unit-student', 'new-unit-subject', 'new-unit-grade', 'new-unit-title'].forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.value = '';
|
|
});
|
|
|
|
renderWorksheetsUnits();
|
|
showUnitDetail();
|
|
setWorksheetsStatus('Lerneinheit erstellt', '');
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Fehler', String(e), 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteWorksheetsUnit(unitId) {
|
|
if (!confirm('Lerneinheit wirklich loeschen?')) return;
|
|
|
|
try {
|
|
setWorksheetsStatus('Loesche Lerneinheit...', '', 'busy');
|
|
const resp = await fetch(`/api/learning-units/${unitId}`, { method: 'DELETE' });
|
|
|
|
if (!resp.ok) {
|
|
throw new Error('Failed to delete unit');
|
|
}
|
|
|
|
worksheetsUnits = worksheetsUnits.filter(u => u.id !== unitId);
|
|
if (worksheetsCurrentUnit?.id === unitId) {
|
|
worksheetsCurrentUnit = null;
|
|
}
|
|
|
|
renderWorksheetsUnits();
|
|
setWorksheetsStatus('Lerneinheit geloescht', '');
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Fehler', String(e), 'error');
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// FILE UPLOAD
|
|
// =============================================
|
|
|
|
function initWorksheetsUpload() {
|
|
const dropZone = document.getElementById('worksheets-upload-zone');
|
|
const fileInput = document.getElementById('worksheets-file-input');
|
|
|
|
if (!dropZone || !fileInput) return;
|
|
|
|
dropZone.addEventListener('click', () => fileInput.click());
|
|
|
|
dropZone.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.add('dragover');
|
|
});
|
|
|
|
dropZone.addEventListener('dragleave', () => {
|
|
dropZone.classList.remove('dragover');
|
|
});
|
|
|
|
dropZone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
dropZone.classList.remove('dragover');
|
|
if (e.dataTransfer.files.length) {
|
|
uploadWorksheetsFiles(e.dataTransfer.files);
|
|
}
|
|
});
|
|
|
|
fileInput.addEventListener('change', () => {
|
|
if (fileInput.files.length) {
|
|
uploadWorksheetsFiles(fileInput.files);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function uploadWorksheetsFiles(files) {
|
|
const formData = new FormData();
|
|
for (const file of files) {
|
|
formData.append('files', file);
|
|
}
|
|
|
|
try {
|
|
setWorksheetsStatus('Lade hoch...', `${files.length} Datei(en)`, 'busy');
|
|
|
|
const resp = await fetch('/api/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
throw new Error('Upload failed');
|
|
}
|
|
|
|
setWorksheetsStatus('Upload erfolgreich', `${files.length} Datei(en)`);
|
|
|
|
// If unit selected, offer to add files
|
|
if (worksheetsCurrentUnit) {
|
|
// Could auto-add here
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Upload fehlgeschlagen', String(e), 'error');
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// GENERATORS - Connected to /api/worksheets/
|
|
// =============================================
|
|
|
|
let generatorDifficulty = 'medium';
|
|
let generatorNumQuestions = 10;
|
|
let generatedContent = null;
|
|
|
|
function selectGeneratorOption(el, difficulty) {
|
|
document.querySelectorAll('.generator-option').forEach(o => o.classList.remove('selected'));
|
|
el.classList.add('selected');
|
|
generatorDifficulty = difficulty;
|
|
generatorNumQuestions = difficulty === 'easy' ? 5 : difficulty === 'medium' ? 10 : 15;
|
|
}
|
|
|
|
// Get source text from current unit or prompt user
|
|
async function getSourceText() {
|
|
if (worksheetsCurrentUnit && worksheetsCurrentUnit.worksheet_files?.length > 0) {
|
|
// In production: extract text from files via OCR/parser
|
|
// For now, prompt user for text input
|
|
const text = prompt('Gib den Quelltext ein (min. 50 Zeichen) oder fuege Inhalt aus deiner Lerneinheit ein:');
|
|
return text;
|
|
}
|
|
return prompt('Gib den Quelltext ein (min. 50 Zeichen):');
|
|
}
|
|
|
|
async function generateMC() {
|
|
const sourceText = await getSourceText();
|
|
if (!sourceText || sourceText.length < 50) {
|
|
alert('Bitte einen laengeren Text eingeben (mind. 50 Zeichen).');
|
|
return;
|
|
}
|
|
|
|
setWorksheetsStatus('Generiere MC-Test...', '', 'busy');
|
|
|
|
try {
|
|
const resp = await fetch('/api/worksheets/generate/multiple-choice', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
source_text: sourceText,
|
|
num_questions: generatorNumQuestions,
|
|
difficulty: generatorDifficulty,
|
|
topic: worksheetsCurrentUnit?.title || 'Lerneinheit',
|
|
subject: worksheetsCurrentUnit?.subject || null
|
|
})
|
|
});
|
|
|
|
const result = await resp.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Generation failed');
|
|
}
|
|
|
|
generatedContent = result.content;
|
|
setWorksheetsStatus('MC-Test generiert', `${result.content.data.questions.length} Fragen`);
|
|
showGeneratedMC(result.content);
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Generation fehlgeschlagen', String(e), 'error');
|
|
alert('Fehler bei der MC-Generierung: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function showGeneratedMC(content) {
|
|
const questions = content.data.questions;
|
|
const container = document.querySelector('#worksheets-subpanel-mc .subpanel-content');
|
|
|
|
let html = `
|
|
<div style="background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
|
|
<h3 style="margin-bottom: 16px;">Generierte Fragen (${questions.length})</h3>
|
|
<div style="max-height: 400px; overflow-y: auto;">
|
|
`;
|
|
|
|
questions.forEach((q, i) => {
|
|
html += `
|
|
<div style="padding: 12px; margin-bottom: 12px; background: var(--bp-bg); border-radius: 8px;">
|
|
<strong>Frage ${i + 1}:</strong> ${q.question}
|
|
<ul style="margin: 8px 0 0 20px;">
|
|
${q.options.map(opt => `
|
|
<li style="color: ${opt.is_correct ? '#10b981' : 'var(--bp-text)'};">
|
|
${opt.text} ${opt.is_correct ? '✓' : ''}
|
|
</li>
|
|
`).join('')}
|
|
</ul>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</div>
|
|
<div style="display: flex; gap: 12px; margin-top: 16px;">
|
|
<button class="btn btn-primary" onclick="exportGeneratedContent('h5p')">Als H5P exportieren</button>
|
|
<button class="btn btn-ghost" onclick="exportGeneratedContent('json')">Als JSON exportieren</button>
|
|
<button class="btn btn-ghost" onclick="printGeneratedContent()">Drucken</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async function generateCloze() {
|
|
const sourceText = await getSourceText();
|
|
if (!sourceText || sourceText.length < 50) {
|
|
alert('Bitte einen laengeren Text eingeben (mind. 50 Zeichen).');
|
|
return;
|
|
}
|
|
|
|
setWorksheetsStatus('Generiere Lueckentext...', '', 'busy');
|
|
|
|
try {
|
|
const resp = await fetch('/api/worksheets/generate/cloze', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
source_text: sourceText,
|
|
num_gaps: generatorDifficulty === 'easy' ? 3 : generatorDifficulty === 'medium' ? 5 : 8,
|
|
difficulty: generatorDifficulty,
|
|
cloze_type: 'fill_in',
|
|
topic: worksheetsCurrentUnit?.title || null
|
|
})
|
|
});
|
|
|
|
const result = await resp.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Generation failed');
|
|
}
|
|
|
|
generatedContent = result.content;
|
|
setWorksheetsStatus('Lueckentext generiert', `${result.content.data.gaps.length} Luecken`);
|
|
showGeneratedCloze(result.content);
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Generation fehlgeschlagen', String(e), 'error');
|
|
alert('Fehler bei der Lueckentext-Generierung: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function showGeneratedCloze(content) {
|
|
const container = document.querySelector('#worksheets-subpanel-cloze .subpanel-content');
|
|
const clozeData = content.data;
|
|
|
|
let html = `
|
|
<div style="background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
|
|
<h3 style="margin-bottom: 16px;">Generierter Lueckentext</h3>
|
|
<div style="padding: 16px; background: var(--bp-bg); border-radius: 8px; line-height: 1.8; margin-bottom: 16px;">
|
|
${clozeData.text_with_gaps}
|
|
</div>
|
|
<h4 style="margin: 16px 0 8px;">Loesungen:</h4>
|
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
${clozeData.gaps.map((g, i) => `
|
|
<span style="padding: 4px 12px; background: #10b981; color: white; border-radius: 16px; font-size: 13px;">
|
|
${i + 1}. ${g.answer}
|
|
</span>
|
|
`).join('')}
|
|
</div>
|
|
<div style="display: flex; gap: 12px; margin-top: 16px;">
|
|
<button class="btn btn-primary" onclick="exportGeneratedContent('h5p')">Als H5P exportieren</button>
|
|
<button class="btn btn-ghost" onclick="printGeneratedContent()">Drucken</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async function generateMindmap() {
|
|
const sourceText = await getSourceText();
|
|
if (!sourceText || sourceText.length < 50) {
|
|
alert('Bitte einen laengeren Text eingeben (mind. 50 Zeichen).');
|
|
return;
|
|
}
|
|
|
|
setWorksheetsStatus('Generiere Mindmap...', '', 'busy');
|
|
|
|
try {
|
|
const resp = await fetch('/api/worksheets/generate/mindmap', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
source_text: sourceText,
|
|
topic: worksheetsCurrentUnit?.title || 'Thema',
|
|
max_depth: 3
|
|
})
|
|
});
|
|
|
|
const result = await resp.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Generation failed');
|
|
}
|
|
|
|
generatedContent = result.content;
|
|
setWorksheetsStatus('Mindmap generiert', `${result.content.data.mindmap.total_nodes} Knoten`);
|
|
showGeneratedMindmap(result.content);
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Generation fehlgeschlagen', String(e), 'error');
|
|
alert('Fehler bei der Mindmap-Generierung: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function showGeneratedMindmap(content) {
|
|
const container = document.querySelector('#worksheets-subpanel-mindmap .subpanel-content');
|
|
const mindmap = content.data.mindmap;
|
|
const mermaid = content.data.mermaid;
|
|
|
|
let html = `
|
|
<div style="background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
|
|
<h3 style="margin-bottom: 16px;">Generierte Mindmap: ${mindmap.title}</h3>
|
|
|
|
<div style="padding: 16px; background: var(--bp-bg); border-radius: 8px; margin-bottom: 16px;">
|
|
<pre style="font-family: monospace; font-size: 12px; white-space: pre-wrap;">${mermaid}</pre>
|
|
</div>
|
|
|
|
<h4 style="margin: 16px 0 8px;">Struktur:</h4>
|
|
<div style="padding: 16px; background: var(--bp-bg); border-radius: 8px;">
|
|
${renderMindmapNode(mindmap.root)}
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 12px; margin-top: 16px;">
|
|
<button class="btn btn-primary" onclick="copyMermaidCode()">Mermaid-Code kopieren</button>
|
|
<button class="btn btn-ghost" onclick="exportGeneratedContent('json')">Als JSON exportieren</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function renderMindmapNode(node, indent = 0) {
|
|
const padding = indent * 20;
|
|
let html = `<div style="padding-left: ${padding}px; margin: 4px 0;">
|
|
<span style="color: ${node.color || 'var(--bp-text)'}; font-weight: ${indent === 0 ? 'bold' : 'normal'};">
|
|
${indent > 0 ? '├─ ' : ''}${node.label}
|
|
</span>
|
|
</div>`;
|
|
|
|
if (node.children) {
|
|
node.children.forEach(child => {
|
|
html += renderMindmapNode(child, indent + 1);
|
|
});
|
|
}
|
|
return html;
|
|
}
|
|
|
|
function copyMermaidCode() {
|
|
if (generatedContent?.data?.mermaid) {
|
|
navigator.clipboard.writeText(generatedContent.data.mermaid);
|
|
alert('Mermaid-Code in Zwischenablage kopiert!');
|
|
}
|
|
}
|
|
|
|
async function generateQA() {
|
|
const sourceText = await getSourceText();
|
|
if (!sourceText || sourceText.length < 50) {
|
|
alert('Bitte einen laengeren Text eingeben (mind. 50 Zeichen).');
|
|
return;
|
|
}
|
|
|
|
setWorksheetsStatus('Generiere Quiz...', '', 'busy');
|
|
|
|
try {
|
|
const resp = await fetch('/api/worksheets/generate/quiz', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
source_text: sourceText,
|
|
quiz_types: ['true_false', 'matching'],
|
|
num_items: generatorDifficulty === 'easy' ? 3 : 5,
|
|
difficulty: generatorDifficulty,
|
|
topic: worksheetsCurrentUnit?.title || null
|
|
})
|
|
});
|
|
|
|
const result = await resp.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Generation failed');
|
|
}
|
|
|
|
generatedContent = result.content;
|
|
const totalItems = (result.content.data.true_false_questions?.length || 0) +
|
|
(result.content.data.matching_pairs?.length || 0);
|
|
setWorksheetsStatus('Quiz generiert', `${totalItems} Items`);
|
|
showGeneratedQuiz(result.content);
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
setWorksheetsStatus('Generation fehlgeschlagen', String(e), 'error');
|
|
alert('Fehler bei der Quiz-Generierung: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function showGeneratedQuiz(content) {
|
|
const container = document.querySelector('#worksheets-subpanel-qa .subpanel-content');
|
|
const quiz = content.data;
|
|
|
|
let html = `
|
|
<div style="background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
|
|
<h3 style="margin-bottom: 16px;">Generiertes Quiz</h3>
|
|
`;
|
|
|
|
if (quiz.true_false_questions?.length > 0) {
|
|
html += `<h4 style="margin: 16px 0 8px;">Richtig/Falsch Fragen:</h4>`;
|
|
quiz.true_false_questions.forEach((q, i) => {
|
|
html += `
|
|
<div style="padding: 12px; margin-bottom: 8px; background: var(--bp-bg); border-radius: 8px;">
|
|
<strong>${i + 1}.</strong> ${q.statement}
|
|
<span style="color: ${q.is_true ? '#10b981' : '#ef4444'}; margin-left: 12px;">
|
|
${q.is_true ? '✓ Richtig' : '✗ Falsch'}
|
|
</span>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
if (quiz.matching_pairs?.length > 0) {
|
|
html += `<h4 style="margin: 16px 0 8px;">Zuordnungsaufgaben:</h4>`;
|
|
quiz.matching_pairs.forEach((p, i) => {
|
|
html += `
|
|
<div style="padding: 12px; margin-bottom: 8px; background: var(--bp-bg); border-radius: 8px; display: flex; align-items: center; gap: 12px;">
|
|
<span style="flex: 1;">${p.left}</span>
|
|
<span style="color: var(--bp-text-muted);">↔</span>
|
|
<span style="flex: 1;">${p.right}</span>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
html += `
|
|
<div style="display: flex; gap: 12px; margin-top: 16px;">
|
|
<button class="btn btn-primary" onclick="exportGeneratedContent('h5p')">Als H5P exportieren</button>
|
|
<button class="btn btn-ghost" onclick="printGeneratedContent()">Drucken</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// Export functions
|
|
async function exportGeneratedContent(format) {
|
|
if (!generatedContent) {
|
|
alert('Kein generierter Inhalt vorhanden.');
|
|
return;
|
|
}
|
|
|
|
if (format === 'h5p' && generatedContent.h5p_format) {
|
|
// Download H5P format as JSON (real H5P package would need backend support)
|
|
const blob = new Blob([JSON.stringify(generatedContent.h5p_format, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `content_h5p_${generatedContent.id}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
} else if (format === 'json') {
|
|
const blob = new Blob([JSON.stringify(generatedContent.data, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `content_${generatedContent.content_type}_${generatedContent.id}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
}
|
|
|
|
function printGeneratedContent() {
|
|
window.print();
|
|
}
|
|
|
|
function createLernflow() {
|
|
alert('Lernflow-Editor: Kombiniere mehrere Generatoren zu einem interaktiven Lernpfad. Kommt in einer spaeteren Version!');
|
|
}
|
|
|
|
function selectLanguage(lang) {
|
|
alert('Uebersetzung nach ' + lang.toUpperCase() + ': Diese Funktion wird mit DeepL/LLM-Integration umgesetzt. Kommt bald!');
|
|
}
|
|
|
|
// =============================================
|
|
// LIGHTBOX
|
|
// =============================================
|
|
|
|
function initWorksheetsLightbox() {
|
|
const lightbox = document.getElementById('lightbox');
|
|
const closeBtn = document.getElementById('lightbox-close');
|
|
|
|
if (closeBtn) {
|
|
closeBtn.addEventListener('click', closeLightbox);
|
|
}
|
|
if (lightbox) {
|
|
lightbox.addEventListener('click', (e) => {
|
|
if (e.target === lightbox) closeLightbox();
|
|
});
|
|
}
|
|
}
|
|
|
|
function openLightbox(src, caption) {
|
|
const lightbox = document.getElementById('lightbox');
|
|
const img = document.getElementById('lightbox-img');
|
|
const captionEl = document.getElementById('lightbox-caption');
|
|
|
|
if (!lightbox || !src) return;
|
|
|
|
img.src = src;
|
|
captionEl.textContent = caption || '';
|
|
lightbox.classList.remove('hidden');
|
|
}
|
|
|
|
function closeLightbox() {
|
|
const lightbox = document.getElementById('lightbox');
|
|
if (lightbox) lightbox.classList.add('hidden');
|
|
}
|
|
|
|
// =============================================
|
|
// SHOW PANEL
|
|
// =============================================
|
|
|
|
function showWorksheetsPanel() {
|
|
console.log('showWorksheetsPanel called');
|
|
hideAllPanels();
|
|
if (typeof hideStudioSubMenu === 'function') hideStudioSubMenu();
|
|
const panel = document.getElementById('panel-worksheets');
|
|
if (panel) {
|
|
panel.style.display = 'flex';
|
|
loadWorksheetsModule();
|
|
console.log('Worksheets panel shown');
|
|
} else {
|
|
console.error('panel-worksheets not found');
|
|
}
|
|
}
|
|
|
|
// Escape key to close subpanels
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
closeLightbox();
|
|
if (document.querySelector('.worksheets-subpanel.active')) {
|
|
closeWorksheetsSubpanel();
|
|
}
|
|
}
|
|
});
|
|
"""
|