Update 13 files

- /TeacherDashboard/index.html
- /TeacherDashboard/TeacherDashboard.code-workspace
- /TeacherDashboard/components/resourceModal.html
- /TeacherDashboard/components/subjectModal.html
- /TeacherDashboard/components/topicModal.html
- /TeacherDashboard/js/main.js
- /TeacherDashboard/js/subjects/colors.js
- /TeacherDashboard/js/subjects/subjectManager.js
- /TeacherDashboard/js/subjects/SubjectModel.js
- /TeacherDashboard/js/subjects/SubjectStorage.js
- /TeacherDashboard/js/topics/TopicModel.js
- /TeacherDashboard/js/topics/topicManager.js
- /TeacherDashboard/styles/main.css
This commit is contained in:
Kelvi Yawo Jules Agbessi Awuklu
2024-12-13 13:43:58 +01:00
committed by Matthias Grief
parent 3977f48367
commit 2369f7158d
13 changed files with 1055 additions and 0 deletions

113
TeacherDashboard/js/main.js Normal file
View File

@@ -0,0 +1,113 @@
import { SubjectManager } from './subjects/subjectManager.js';
import { TopicManager } from './topics/topicManager.js';
// Make modal functions globally available
window.openModal = (modalId) => {
const modal = document.getElementById(modalId);
modal.classList.remove('hidden');
if (modalId === 'topicModal') {
document.dispatchEvent(new Event('openTopicModal'));
}
gsap.fromTo(modal.children[0],
{ y: -50, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.3 }
);
};
window.closeModal = (modalId) => {
const modal = document.getElementById(modalId);
gsap.to(modal.children[0], {
y: -50,
opacity: 0,
duration: 0.3,
onComplete: () => {
modal.classList.add('hidden');
}
});
};
// Make this function globally available
window.openSubjectModal = () => {
openModal('subjectModal');
};
async function loadModals() {
const modalContainer = document.getElementById('modalContainer');
const modals = ['subjectModal', 'topicModal', 'resourceModal'];
for (const modal of modals) {
try {
const response = await fetch(`components/${modal}.html`);
const html = await response.text();
modalContainer.innerHTML += html;
} catch (error) {
console.error(`Error loading ${modal}:`, error);
}
}
// Initialize color picker functionality for subject modal
initializeColorPicker();
}
function initializeColorPicker() {
const colorOptions = document.querySelectorAll('.color-option');
colorOptions.forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.color-option').forEach(opt =>
opt.classList.remove('ring-2', 'ring-blue-500'));
this.classList.add('ring-2', 'ring-blue-500');
document.getElementById('selectedColor').value = this.dataset.color;
});
});
}
// Initialize everything after DOM content is loaded
document.addEventListener('DOMContentLoaded', async () => {
await loadModals();
const subjectManager = new SubjectManager();
// Make topicManager globally available
window.topicManager = new TopicManager();
// Initialize event listeners for the subject form
const form = document.getElementById('subjectForm');
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const submitButton = form.querySelector('button[type="submit"]');
submitButton.disabled = true;
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Speichern...';
try {
await subjectManager.handleSubjectSubmit(e);
showNotification('Fach erfolgreich erstellt!', 'success');
} catch (error) {
showNotification(error.message || 'Fehler beim Erstellen des Fachs', 'error');
} finally {
submitButton.disabled = false;
submitButton.innerHTML = 'Speichern';
}
});
}
});
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `fixed bottom-4 right-4 p-4 rounded-lg text-white ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
notification.textContent = message;
document.body.appendChild(notification);
gsap.to(notification, {
opacity: 0,
y: 20,
delay: 3,
duration: 0.5,
onComplete: () => notification.remove()
});
}
// Form submissions and other JavaScript functions
// ... rest of your JavaScript code ...

View File

@@ -0,0 +1,47 @@
export class SubjectModel {
constructor(data) {
this.id = data.id || crypto.randomUUID();
this.name = data.name;
this.description = data.description || '';
this.color = data.color;
this.icon = data.icon;
this.createdAt = data.createdAt || new Date().toISOString();
this.updatedAt = new Date().toISOString();
this.topics = data.topics || [];
this.resources = data.resources || [];
this.metadata = {
lastAccessed: new Date().toISOString(),
topicCount: 0,
resourceCount: 0
};
this.content = data.content || '';
this.materials = data.materials || [];
}
validate() {
const required = ['name', 'color', 'icon'];
const missing = required.filter(field => !this[field]);
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(', ')}`);
}
return true;
}
toJSON() {
return {
id: this.id,
name: this.name,
description: this.description,
color: this.color,
icon: this.icon,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
topics: this.topics,
resources: this.resources,
metadata: this.metadata,
content: this.content,
materials: this.materials
};
}
}

View File

@@ -0,0 +1,39 @@
export class SubjectStorage {
constructor() {
this.storageKey = 'teacherDash_subjects';
}
async getAllSubjects() {
try {
const data = localStorage.getItem(this.storageKey);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('Error loading subjects:', error);
return [];
}
}
async saveSubject(subject) {
try {
const subjects = await this.getAllSubjects();
const existingIndex = subjects.findIndex(s => s.id === subject.id);
if (existingIndex >= 0) {
subjects[existingIndex] = subject;
} else {
subjects.push(subject);
}
localStorage.setItem(this.storageKey, JSON.stringify(subjects));
return subject;
} catch (error) {
throw new Error('Failed to save subject: ' + error.message);
}
}
async deleteSubject(subjectId) {
const subjects = await this.getAllSubjects();
const filtered = subjects.filter(s => s.id !== subjectId);
localStorage.setItem(this.storageKey, JSON.stringify(filtered));
}
}

View File

@@ -0,0 +1,21 @@
export const subjectColors = {
red: { hex: '#FF6B6B', name: 'Rot' },
teal: { hex: '#4ECDC4', name: 'Türkis' },
blue: { hex: '#45B7D1', name: 'Blau' },
green: { hex: '#96CEB4', name: 'Grün' },
brown: { hex: '#D4A373', name: 'Braun' },
purple: { hex: '#9B5DE5', name: 'Lila' },
pink: { hex: '#F15BB5', name: 'Pink' },
yellow: { hex: '#FEE440', name: 'Gelb' },
lightBlue: { hex: '#00BBF9', name: 'Hellblau' },
mint: { hex: '#00F5D4', name: 'Mint' }
};
export function generateColorPalette() {
return Object.entries(subjectColors)
.map(([key, color]) => ({
id: key,
hex: color.hex,
name: color.name
}));
}

View File

@@ -0,0 +1,139 @@
import { generateColorPalette } from './colors.js';
import { SubjectModel } from './SubjectModel.js';
import { SubjectStorage } from './SubjectStorage.js';
export class SubjectManager {
constructor() {
this.storage = new SubjectStorage();
this.subjects = [];
this.initializeEventListeners();
}
initializeEventListeners() {
const form = document.getElementById('subjectForm');
if (form) {
form.addEventListener('submit', (e) => this.handleSubjectSubmit(e));
}
this.initializeColorPicker();
this.initializeIconPicker();
}
initializeColorPicker() {
const palette = generateColorPalette();
const container = document.getElementById('colorPalette');
if (container) {
container.innerHTML = palette.map(color => `
<div class="color-option"
style="background-color: ${color.hex}"
data-color="${color.hex}"
title="${color.name}">
</div>
`).join('');
this.addColorPickerListeners();
}
}
initializeIconPicker() {
const iconOptions = document.querySelectorAll('.icon-option');
iconOptions.forEach(option => {
option.addEventListener('click', () => {
iconOptions.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected');
document.getElementById('selectedIcon').value = option.dataset.icon;
});
});
}
addColorPickerListeners() {
const colorOptions = document.querySelectorAll('.color-option');
colorOptions.forEach(option => {
option.addEventListener('click', () => {
colorOptions.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected');
document.getElementById('selectedColor').value = option.dataset.color;
});
});
}
async handleSubjectSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
try {
const subjectData = {
name: formData.get('displayName'),
description: formData.get('description'),
color: formData.get('color'),
icon: formData.get('icon'),
materials: await this.processFiles(formData.getAll('materials[]'))
};
const subject = new SubjectModel(subjectData);
subject.validate();
await this.storage.saveSubject(subject.toJSON());
this.updateSubjectsList(subject.toJSON());
closeModal('subjectModal');
e.target.reset();
return subject;
} catch (error) {
throw new Error(`Failed to create subject: ${error.message}`);
}
}
async processFiles(files) {
return Array.from(files).map(file => ({
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
}));
}
async loadSubjects() {
const subjects = await this.storage.getAllSubjects();
subjects.forEach(subject => this.updateSubjectsList(subject));
}
validateSubjectForm(formData) {
const requiredFields = ['displayName', 'color', 'icon'];
return requiredFields.every(field => formData.get(field)?.trim());
}
updateSubjectsList(subject) {
const container = document.getElementById('subjectsList');
if (!container) {
const mainContent = document.querySelector('main');
const subjectsSection = document.createElement('div');
subjectsSection.className = 'mt-8';
subjectsSection.innerHTML = `
<h2 class="text-xl font-semibold mb-4">Meine Fächer</h2>
<div id="subjectsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
</div>
`;
mainContent.appendChild(subjectsSection);
}
const subjectElement = this.createSubjectElement(subject);
document.getElementById('subjectsList').appendChild(subjectElement);
}
createSubjectElement(subject) {
const div = document.createElement('div');
div.className = 'bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4';
div.innerHTML = `
<div class="w-10 h-10 rounded-lg flex items-center justify-center" style="background-color: ${subject.color}">
<i class="fas ${subject.icon} text-white"></i>
</div>
<div>
<h3 class="font-medium">${subject.name}</h3>
<p class="text-sm text-gray-500">${subject.description || ''}</p>
</div>
`;
return div;
}
}

View File

View File

@@ -0,0 +1,178 @@
import { SubjectStorage } from '../subjects/SubjectStorage.js';
export class TopicManager {
constructor() {
this.subjectStorage = new SubjectStorage();
this.quill = null;
this.initializeEventListeners();
}
initializeEditor() {
const toolbarOptions = [
['undo', 'redo'],
[{ 'header': [1, 2, false] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
['link', 'image', 'formula'],
['clean']
];
const editorOptions = {
modules: {
toolbar: toolbarOptions,
keyboard: {
bindings: {
custom1: {
key: 'Z',
shortKey: true,
handler: () => this.undo()
},
custom2: {
key: 'Z',
shortKey: true,
shiftKey: true,
handler: () => this.redo()
},
custom3: {
key: 'Delete',
handler: () => this.deleteSelected()
}
}
},
history: {
delay: 1000,
maxStack: 500,
userOnly: true
}
},
theme: 'snow',
formats: [
'header',
'bold', 'italic', 'underline', 'strike',
'list', 'bullet',
'indent',
'link', 'image',
'code-block',
'blockquote',
'clean'
]
};
this.quill = new Quill('#quillEditor', editorOptions);
this.quill.on('text-change', () => {
this.updatePreview();
});
}
undo() {
if (this.quill) {
this.quill.history.undo();
}
}
redo() {
if (this.quill) {
this.quill.history.redo();
}
}
deleteSelected() {
if (this.quill) {
const range = this.quill.getSelection();
if (range) {
if (range.length > 0) {
// Delete selected text/content
this.quill.deleteText(range.index, range.length);
} else {
// Delete current line if no selection
const [line] = this.quill.getLine(range.index);
const lineLength = line.length();
this.quill.deleteText(range.index - lineLength, lineLength);
}
}
}
}
async loadSubjectsIntoSelect() {
const select = document.getElementById('topicSubjectSelect');
if (!select) return;
// Clear existing options except the first placeholder
while (select.options.length > 1) {
select.remove(1);
}
try {
const subjects = await this.subjectStorage.getAllSubjects();
subjects.forEach(subject => {
const option = document.createElement('option');
option.value = subject.id;
option.textContent = subject.name;
select.appendChild(option);
});
} catch (error) {
console.error('Error loading subjects:', error);
}
}
initializeEventListeners() {
const form = document.getElementById('topicForm');
if (form) {
form.addEventListener('submit', (e) => this.handleTopicSubmit(e));
}
document.addEventListener('openTopicModal', () => {
this.loadSubjectsIntoSelect();
if (!this.quill) {
this.initializeEditor();
}
});
}
updatePreview() {
const content = this.quill.root.innerHTML;
const preview = document.getElementById('contentPreview');
const quillContent = document.getElementById('quillContent');
if (preview) {
// Preserve classes for styling while updating content
preview.innerHTML = content;
// Update hidden input with content
if (quillContent) {
quillContent.value = content;
}
// Re-render math if present
if (window.MathJax) {
MathJax.typeset([preview]);
}
// Highlight code blocks if any
if (window.Prism) {
Prism.highlightAllUnder(preview);
}
}
}
handleTopicSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const content = this.quill.root.innerHTML;
const subjectId = formData.get('subject');
if (!subjectId) {
throw new Error('Bitte wählen Sie ein Fach aus');
}
// Add your topic saving logic here
closeModal('topicModal');
}
clearEditor() {
if (this.quill) {
this.quill.setContents([]);
this.updatePreview();
}
}
}