""" BreakPilot Studio - Content Creator Modul Funktionen: - Educational Content erstellen & verwalten - H5P Interactive Content Editor Integration - Creative Commons Lizenzierung - Upload von Videos, PDFs, Bildern, Audio - Publishing zu Matrix Feed - Analytics & Impact Scoring """ class ContentCreatorModule: """Modul für Content Creation.""" @staticmethod def get_css() -> str: """CSS für das Content Creator Modul.""" return """ /* ============================================= CONTENT CREATOR MODULE ============================================= */ .panel-content-creator { display: flex; flex-direction: column; height: 100%; background: var(--bp-bg); overflow-y: auto; } /* Header */ .content-creator-header { padding: 32px 40px 24px; background: var(--bp-surface); border-bottom: 1px solid var(--bp-border); } .content-creator-header h2 { font-size: 24px; font-weight: 700; color: var(--bp-text); margin-bottom: 8px; } .content-creator-header p { font-size: 14px; color: var(--bp-text-muted); } /* Navigation Tabs */ .content-creator-tabs { display: flex; gap: 8px; margin-top: 16px; } .creator-tab { padding: 10px 20px; background: transparent; border: 1px solid var(--bp-border); border-radius: 8px; color: var(--bp-text-muted); font-size: 14px; cursor: pointer; transition: all 0.2s; } .creator-tab:hover { background: var(--bp-surface-elevated); border-color: var(--bp-primary); } .creator-tab.active { background: var(--bp-primary); border-color: var(--bp-primary); color: white; } /* Content Area */ .content-creator-content { padding: 32px 40px; flex: 1; } /* Tab Panels */ .creator-tab-panel { display: none; } .creator-tab-panel.active { display: block; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* Create Content Form */ .create-content-form { max-width: 800px; background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 32px; } .form-section { margin-bottom: 32px; } .form-section-title { font-size: 16px; font-weight: 600; color: var(--bp-text); margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid var(--bp-border); } .form-group { margin-bottom: 20px; } .form-label { display: block; font-size: 14px; font-weight: 500; color: var(--bp-text); margin-bottom: 8px; } .form-label-required::after { content: ' *'; color: var(--bp-danger); } .form-input, .form-select, .form-textarea { width: 100%; padding: 12px 16px; background: var(--bp-bg); border: 1px solid var(--bp-border); border-radius: 8px; color: var(--bp-text); font-size: 14px; font-family: inherit; transition: all 0.2s; } .form-input:focus, .form-select:focus, .form-textarea:focus { outline: none; border-color: var(--bp-primary); box-shadow: 0 0 0 3px var(--bp-primary-soft); } .form-textarea { min-height: 100px; resize: vertical; } .form-hint { font-size: 12px; color: var(--bp-text-muted); margin-top: 6px; } /* File Upload Area */ .file-upload-area { border: 2px dashed var(--bp-border); border-radius: 12px; padding: 40px; text-align: center; background: var(--bp-bg); cursor: pointer; transition: all 0.3s; } .file-upload-area:hover { border-color: var(--bp-primary); background: var(--bp-primary-soft); } .file-upload-area.dragover { border-color: var(--bp-primary); background: var(--bp-primary-soft); transform: scale(1.02); } .upload-icon { font-size: 48px; color: var(--bp-text-muted); margin-bottom: 16px; } .upload-text { font-size: 16px; color: var(--bp-text); font-weight: 500; margin-bottom: 8px; } .upload-hint { font-size: 13px; color: var(--bp-text-muted); } .file-input-hidden { display: none; } /* Uploaded Files List */ .uploaded-files { margin-top: 16px; } .uploaded-file { display: flex; align-items: center; gap: 12px; padding: 12px; background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 8px; margin-bottom: 8px; } .file-icon { font-size: 24px; color: var(--bp-primary); } .file-info { flex: 1; } .file-name { font-size: 14px; font-weight: 500; color: var(--bp-text); } .file-size { font-size: 12px; color: var(--bp-text-muted); } .file-remove { padding: 6px 12px; background: transparent; border: none; color: var(--bp-danger); cursor: pointer; font-size: 12px; transition: opacity 0.2s; } .file-remove:hover { opacity: 0.7; } /* License Selector */ .license-options { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; } .license-option { padding: 16px; background: var(--bp-bg); border: 2px solid var(--bp-border); border-radius: 8px; cursor: pointer; transition: all 0.2s; text-align: center; } .license-option:hover { border-color: var(--bp-primary); background: var(--bp-primary-soft); } .license-option.selected { border-color: var(--bp-primary); background: var(--bp-primary-soft); } .license-badge { font-size: 24px; margin-bottom: 8px; } .license-name { font-size: 13px; font-weight: 600; color: var(--bp-text); margin-bottom: 4px; } .license-desc { font-size: 11px; color: var(--bp-text-muted); } /* Category Pills */ .category-pills { display: flex; flex-wrap: wrap; gap: 8px; } .category-pill { padding: 8px 16px; background: var(--bp-bg); border: 1px solid var(--bp-border); border-radius: 999px; font-size: 13px; color: var(--bp-text); cursor: pointer; transition: all 0.2s; } .category-pill:hover { border-color: var(--bp-primary); background: var(--bp-primary-soft); } .category-pill.selected { background: var(--bp-primary); border-color: var(--bp-primary); color: white; } /* Tags Input */ .tags-container { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px; background: var(--bp-bg); border: 1px solid var(--bp-border); border-radius: 8px; min-height: 42px; } .tag { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: var(--bp-primary-soft); border: 1px solid var(--bp-primary); border-radius: 4px; font-size: 12px; color: var(--bp-primary); } .tag-remove { cursor: pointer; font-weight: 700; opacity: 0.7; } .tag-remove:hover { opacity: 1; } .tag-input { flex: 1; min-width: 150px; border: none; background: transparent; color: var(--bp-text); font-size: 14px; outline: none; } /* H5P Integration */ .h5p-editor-container { margin-top: 16px; border: 1px solid var(--bp-border); border-radius: 12px; overflow: hidden; background: white; } .h5p-editor-frame { width: 100%; min-height: 600px; border: none; } /* My Content Grid */ .my-content-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 24px; } .content-card { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; overflow: hidden; transition: all 0.3s; } .content-card:hover { transform: translateY(-4px); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } .content-thumbnail { width: 100%; height: 180px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; font-size: 48px; color: white; } .content-body { padding: 20px; } .content-title { font-size: 16px; font-weight: 600; color: var(--bp-text); margin-bottom: 8px; } .content-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; } .meta-badge { padding: 4px 10px; background: var(--bp-bg); border-radius: 4px; font-size: 11px; color: var(--bp-text-muted); } .content-stats { display: flex; gap: 16px; padding-top: 12px; border-top: 1px solid var(--bp-border); font-size: 12px; color: var(--bp-text-muted); } .stat { display: flex; align-items: center; gap: 4px; } .content-actions { display: flex; gap: 8px; margin-top: 12px; } .btn-icon { padding: 8px; background: transparent; border: 1px solid var(--bp-border); border-radius: 6px; color: var(--bp-text-muted); cursor: pointer; transition: all 0.2s; } .btn-icon:hover { background: var(--bp-primary); border-color: var(--bp-primary); color: white; } /* Analytics Dashboard */ .analytics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 24px; margin-bottom: 32px; } .analytics-card { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 24px; } .analytics-label { font-size: 13px; color: var(--bp-text-muted); margin-bottom: 8px; } .analytics-value { font-size: 32px; font-weight: 700; color: var(--bp-text); margin-bottom: 4px; } .analytics-change { font-size: 12px; color: var(--bp-success); } .analytics-change.negative { color: var(--bp-danger); } /* Action Buttons */ .form-actions { display: flex; gap: 12px; margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--bp-border); } .btn-primary { flex: 1; padding: 14px 24px; background: var(--bp-primary); border: none; border-radius: 8px; color: white; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .btn-primary:hover { background: var(--bp-primary-hover); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(108, 27, 27, 0.3); } .btn-secondary { padding: 14px 24px; background: transparent; border: 1px solid var(--bp-border); border-radius: 8px; color: var(--bp-text); font-size: 15px; font-weight: 500; cursor: pointer; transition: all 0.2s; } .btn-secondary:hover { background: var(--bp-surface-elevated); border-color: var(--bp-primary); } /* Status Badge */ .status-badge { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; } .status-draft { background: rgba(100, 116, 139, 0.15); color: #64748b; } .status-published { background: rgba(34, 197, 94, 0.15); color: #22c55e; } .status-review { background: rgba(245, 158, 11, 0.15); color: #f59e0b; } """ @staticmethod def get_html() -> str: """HTML für das Content Creator Modul.""" return """ """ @staticmethod def get_js() -> str: """JavaScript für das Content Creator Modul.""" return """ // ============================================= // CONTENT CREATOR MODULE // ============================================= (function() { 'use strict'; const ContentCreator = { selectedFiles: [], selectedCategory: null, selectedLicense: 'CC-BY-SA-4.0', tags: [], init() { this.bindTabSwitching(); this.bindFileUpload(); this.bindCategorySelection(); this.bindLicenseSelection(); this.bindTagsInput(); this.bindContentTypeChange(); this.bindFormSubmit(); // Set default license document.querySelector(`[data-license="${this.selectedLicense}"]`)?.classList.add('selected'); }, bindTabSwitching() { const tabs = document.querySelectorAll('.creator-tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { const tabName = tab.dataset.tab; // Update tabs tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Update panels document.querySelectorAll('.creator-tab-panel').forEach(panel => { panel.classList.remove('active'); }); document.getElementById(`tab-${tabName}`)?.classList.add('active'); // Load content if switching to my-content if (tabName === 'my-content') { this.loadMyContent(); } else if (tabName === 'analytics') { this.loadAnalytics(); } }); }); }, bindFileUpload() { const uploadArea = document.getElementById('file-upload-area'); const fileInput = document.getElementById('file-input'); uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); this.handleFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', (e) => { this.handleFiles(e.target.files); }); }, handleFiles(files) { Array.from(files).forEach(file => { if (file.size > 100 * 1024 * 1024) { alert(`Datei ${file.name} ist zu groß (max. 100MB)`); return; } this.selectedFiles.push(file); this.renderUploadedFiles(); }); }, renderUploadedFiles() { const container = document.getElementById('uploaded-files'); container.innerHTML = ''; this.selectedFiles.forEach((file, index) => { const fileEl = document.createElement('div'); fileEl.className = 'uploaded-file'; fileEl.innerHTML = `
${this.getFileIcon(file.type)}
${file.name}
${this.formatFileSize(file.size)}
`; fileEl.querySelector('.file-remove').addEventListener('click', () => { this.selectedFiles.splice(index, 1); this.renderUploadedFiles(); }); container.appendChild(fileEl); }); }, getFileIcon(type) { if (type.startsWith('video/')) return '📹'; if (type.startsWith('image/')) return '🖼️'; if (type.startsWith('audio/')) return '🎵'; if (type === 'application/pdf') return '📄'; return '📁'; }, formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }, bindCategorySelection() { document.querySelectorAll('.category-pill').forEach(pill => { pill.addEventListener('click', () => { document.querySelectorAll('.category-pill').forEach(p => p.classList.remove('selected')); pill.classList.add('selected'); this.selectedCategory = pill.dataset.category; }); }); }, bindLicenseSelection() { document.querySelectorAll('.license-option').forEach(option => { option.addEventListener('click', () => { document.querySelectorAll('.license-option').forEach(o => o.classList.remove('selected')); option.classList.add('selected'); this.selectedLicense = option.dataset.license; }); }); }, bindTagsInput() { const input = document.getElementById('tag-input'); const container = document.getElementById('tags-container'); input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && input.value.trim()) { e.preventDefault(); const tag = input.value.trim(); if (!this.tags.includes(tag)) { this.tags.push(tag); this.renderTags(); } input.value = ''; } }); }, renderTags() { const container = document.getElementById('tags-container'); const input = document.getElementById('tag-input'); // Remove old tags container.querySelectorAll('.tag').forEach(el => el.remove()); // Add new tags this.tags.forEach((tag, index) => { const tagEl = document.createElement('div'); tagEl.className = 'tag'; tagEl.innerHTML = ` ${tag} × `; tagEl.querySelector('.tag-remove').addEventListener('click', () => { this.tags.splice(index, 1); this.renderTags(); }); container.insertBefore(tagEl, input); }); }, bindContentTypeChange() { const typeSelect = document.getElementById('content-type'); typeSelect.addEventListener('change', () => { const h5pSection = document.getElementById('h5p-section'); const fileSection = document.getElementById('file-upload-section'); if (typeSelect.value === 'h5p') { h5pSection.style.display = 'block'; fileSection.style.display = 'none'; } else { h5pSection.style.display = 'none'; fileSection.style.display = 'block'; } }); }, bindFormSubmit() { const form = document.getElementById('create-content-form'); form.addEventListener('submit', async (e) => { e.preventDefault(); await this.publishContent(); }); document.getElementById('btn-save-draft').addEventListener('click', () => { this.saveDraft(); }); }, async publishContent() { if (!this.selectedCategory) { alert('Bitte wählen Sie eine Kategorie aus.'); return; } const data = { title: document.getElementById('content-title').value, description: document.getElementById('content-description').value, content_type: document.getElementById('content-type').value, category: this.selectedCategory, license: this.selectedLicense, age_min: parseInt(document.getElementById('age-min').value), age_max: parseInt(document.getElementById('age-max').value), tags: this.tags, status: 'published' }; try { // Create content const response = await fetch('http://localhost:8002/api/v1/content', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Failed to create content'); const content = await response.json(); // Upload files if any if (this.selectedFiles.length > 0) { await this.uploadFiles(content.id); } alert('✅ Content erfolgreich veröffentlicht und zu Matrix Feed hinzugefügt!'); this.resetForm(); // Switch to my-content tab document.querySelector('[data-tab="my-content"]').click(); } catch (error) { console.error('Error:', error); alert('❌ Fehler beim Veröffentlichen: ' + error.message); } }, async uploadFiles(contentId) { for (const file of this.selectedFiles) { const formData = new FormData(); formData.append('file', file); const response = await fetch('http://localhost:8002/api/v1/upload', { method: 'POST', body: formData }); if (!response.ok) throw new Error('File upload failed'); const { file_url } = await response.json(); // Attach file to content await fetch(`http://localhost:8002/api/v1/content/${contentId}/files`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_urls: [file_url] }) }); } }, saveDraft() { alert('💾 Als Entwurf gespeichert (Feature kommt bald)'); }, resetForm() { document.getElementById('create-content-form').reset(); this.selectedFiles = []; this.selectedCategory = null; this.tags = []; this.renderUploadedFiles(); this.renderTags(); document.querySelectorAll('.category-pill').forEach(p => p.classList.remove('selected')); }, async loadMyContent() { // TODO: Load user's content from API console.log('Loading my content...'); }, async loadAnalytics() { // TODO: Load analytics from API console.log('Loading analytics...'); } }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ContentCreator.init()); } else { ContentCreator.init(); } })(); """