Files
Projektarbeit-MYP/backend/static/js/theme-manager-unified.js
Till Tomczak 956c24d8ca 🔧 Update: Enhanced error handling and logging across various modules
**Änderungen:**
-  app.py: Hinzugefügt, um CSRF-Fehler zu behandeln
-  models.py: Fehlerprotokollierung bei der Suche nach Gastanfragen per OTP
-  api.py: Fehlerprotokollierung beim Markieren von Benachrichtigungen als gelesen
-  calendar.py: Fallback-Daten zurückgeben, wenn keine Kalenderereignisse vorhanden sind
-  guest.py: Status-Check-Seite für Gäste aktualisiert
-  hardware_integration.py: Debugging-Informationen für erweiterte Geräteinformationen hinzugefügt
-  tapo_status_manager.py: Rückgabewert für Statusabfrage hinzugefügt

**Ergebnis:**
- Verbesserte Fehlerbehandlung und Protokollierung für eine robustere Anwendung
- Bessere Nachverfolgbarkeit von Fehlern und Systemverhalten

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 22:45:20 +02:00

525 lines
16 KiB
JavaScript

/**
* Unified Theme Manager für MYP Platform
* =====================================
*
* Konsolidiert alle Dark/Light Mode JavaScript-Implementierungen
* Ersetzt: dark-mode.js, verschiedene inline Scripts
*
* Author: Till Tomczak - MYP Team
* Zweck: Einheitliches Theme-Management und Behebung von Inkonsistenzen
*/
class UnifiedThemeManager {
constructor() {
// Einheitlicher Storage Key (konsolidiert alle verschiedenen Keys)
this.storageKey = 'myp-dark-mode';
this.oldStorageKeys = ['darkMode', 'theme', 'dark-theme']; // Legacy Keys für Migration
// DOM Elemente
this.htmlElement = document.documentElement;
this.toggleButtons = [];
this.themeIndicators = [];
// Event Callbacks
this.callbacks = [];
// Debug Modus
this.debug = false;
// Initialisierung
this.init();
// Globale Instanz für Legacy-Kompatibilität
window.themeManager = this;
}
/**
* Initialisiert den Theme Manager
*/
init() {
this.log('🎨 Unified Theme Manager initialisiert');
// Legacy Storage Keys migrieren
this.migrateLegacyStorage();
// Theme aus Storage laden oder System-Präferenz verwenden
this.loadTheme();
// Event Listener registrieren
this.setupEventListeners();
// DOM-Elemente suchen und registrieren
this.findThemeElements();
// Initial UI aktualisieren
this.updateUI();
// CSS Custom Properties setzen
this.updateCSSProperties();
}
/**
* Migriert alte Storage Keys zum einheitlichen System
*/
migrateLegacyStorage() {
for (const oldKey of this.oldStorageKeys) {
const oldValue = localStorage.getItem(oldKey);
if (oldValue && !localStorage.getItem(this.storageKey)) {
// Normalisiere den Wert
const isDark = oldValue === 'true' || oldValue === 'dark';
localStorage.setItem(this.storageKey, isDark.toString());
localStorage.removeItem(oldKey); // Alten Key entfernen
this.log(`🔄 Migriert ${oldKey}: ${oldValue}${this.storageKey}: ${isDark}`);
}
}
}
/**
* Lädt das Theme aus Storage oder verwendet System-Präferenz
*/
loadTheme() {
let theme = localStorage.getItem(this.storageKey);
if (theme === null) {
// Keine gespeicherte Präferenz → System-Präferenz verwenden
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
theme = prefersDark ? 'true' : 'false';
this.log(`🌟 Keine gespeicherte Präferenz → System: ${prefersDark ? 'Dark' : 'Light'} Mode`);
}
const isDark = theme === 'true';
this.setTheme(isDark, false); // Ohne Storage-Update da bereits aus Storage geladen
}
/**
* Event Listener für System-Präferenz-Änderungen und Keyboard-Shortcuts
*/
setupEventListeners() {
// System-Präferenz-Änderungen
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
// Nur reagieren wenn keine manuelle Präferenz gesetzt wurde
const storedTheme = localStorage.getItem(this.storageKey);
if (storedTheme === null) {
this.setTheme(e.matches);
this.log(`🔄 System-Präferenz geändert: ${e.matches ? 'Dark' : 'Light'} Mode`);
}
});
// Keyboard Shortcut (Ctrl/Cmd + Shift + D)
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'D') {
e.preventDefault();
this.toggle();
this.log('⌨️ Theme per Keyboard-Shortcut umgeschaltet');
}
});
// Page Visibility API für Theme-Synchronisation zwischen Tabs
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
this.syncThemeFromStorage();
}
});
// Storage Event für Cross-Tab-Synchronisation
window.addEventListener('storage', (e) => {
if (e.key === this.storageKey && e.newValue !== null) {
const isDark = e.newValue === 'true';
this.setTheme(isDark, false); // Ohne Storage-Update
this.log(`🔄 Theme aus anderem Tab synchronisiert: ${isDark ? 'Dark' : 'Light'} Mode`);
}
});
// CSS-Transition Event für sanfte Übergänge
this.htmlElement.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
}
/**
* Sucht und registriert alle Theme-bezogenen DOM-Elemente
*/
findThemeElements() {
// Toggle Buttons finden
const toggleSelectors = [
'[data-theme-toggle]',
'.theme-toggle',
'.dark-mode-toggle',
'#theme-toggle',
'#darkModeToggle'
];
this.toggleButtons = [];
toggleSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
this.registerToggleButton(element);
});
});
// Theme Indikatoren finden
const indicatorSelectors = [
'[data-theme-indicator]',
'.theme-indicator',
'.dark-mode-indicator'
];
this.themeIndicators = [];
indicatorSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
this.themeIndicators.push(element);
});
});
this.log(`🔍 Gefunden: ${this.toggleButtons.length} Toggle-Buttons, ${this.themeIndicators.length} Indikatoren`);
}
/**
* Registriert einen Toggle-Button
*/
registerToggleButton(element) {
if (this.toggleButtons.includes(element)) return;
element.addEventListener('click', (e) => {
e.preventDefault();
this.toggle();
});
// Accessibility
element.setAttribute('role', 'button');
element.setAttribute('aria-label', 'Theme umschalten');
this.toggleButtons.push(element);
}
/**
* Theme umschalten
*/
toggle() {
const currentTheme = this.isDarkMode();
const newTheme = !currentTheme;
this.setTheme(newTheme);
this.log(`🔄 Theme umgeschaltet: ${currentTheme ? 'Dark' : 'Light'}${newTheme ? 'Dark' : 'Light'} Mode`);
// Animation für visuelles Feedback
this.triggerToggleAnimation();
}
/**
* Theme setzen
*/
setTheme(isDark, updateStorage = true) {
const previousTheme = this.isDarkMode();
// HTML Klasse setzen/entfernen
this.htmlElement.classList.toggle('dark', isDark);
// Storage aktualisieren
if (updateStorage) {
localStorage.setItem(this.storageKey, isDark.toString());
}
// CSS Custom Properties aktualisieren
this.updateCSSProperties();
// UI-Elemente aktualisieren
this.updateUI();
// Meta Theme-Color aktualisieren
this.updateMetaThemeColor(isDark);
// Callbacks ausführen
this.executeCallbacks(isDark, previousTheme);
// Custom Event dispatchen
this.dispatchThemeEvent(isDark);
this.log(`🎨 Theme gesetzt: ${isDark ? 'Dark' : 'Light'} Mode`);
}
/**
* Aktueller Theme-Status
*/
isDarkMode() {
return this.htmlElement.classList.contains('dark');
}
/**
* Theme als String
*/
getTheme() {
return this.isDarkMode() ? 'dark' : 'light';
}
/**
* UI-Elemente aktualisieren
*/
updateUI() {
const isDark = this.isDarkMode();
const themeText = isDark ? 'Dark' : 'Light';
// Toggle Buttons aktualisieren
this.toggleButtons.forEach(button => {
// Icon aktualisieren
const iconElement = button.querySelector('.theme-icon') || button.querySelector('i') || button;
if (iconElement) {
// Entferne alle Theme-Icon-Klassen
iconElement.classList.remove('fa-sun', 'fa-moon', 'fa-lightbulb', 'fa-moon-o');
// Füge entsprechendes Icon hinzu
if (isDark) {
iconElement.classList.add('fa-sun');
iconElement.title = 'Zu Light Mode wechseln';
} else {
iconElement.classList.add('fa-moon');
iconElement.title = 'Zu Dark Mode wechseln';
}
}
// Text aktualisieren (falls vorhanden)
const textElement = button.querySelector('.theme-text');
if (textElement) {
textElement.textContent = isDark ? 'Light Mode' : 'Dark Mode';
}
// Aria-Label aktualisieren
button.setAttribute('aria-label', `Zu ${isDark ? 'Light' : 'Dark'} Mode wechseln`);
// Data-Attribute für CSS-Styling
button.setAttribute('data-theme', this.getTheme());
});
// Theme Indikatoren aktualisieren
this.themeIndicators.forEach(indicator => {
indicator.textContent = `${themeText} Mode`;
indicator.setAttribute('data-theme', this.getTheme());
});
// Body-Klassen für Legacy-Kompatibilität
document.body.classList.toggle('dark-mode', isDark);
document.body.classList.toggle('light-mode', !isDark);
}
/**
* CSS Custom Properties aktualisieren
*/
updateCSSProperties() {
const isDark = this.isDarkMode();
// Zusätzliche CSS Properties für JavaScript-basierte Komponenten
this.htmlElement.style.setProperty('--theme-mode', isDark ? 'dark' : 'light');
this.htmlElement.style.setProperty('--theme-multiplier', isDark ? '-1' : '1');
// Für Chart.js und andere Libraries
this.htmlElement.style.setProperty('--chart-text-color', isDark ? '#ffffff' : '#111827');
this.htmlElement.style.setProperty('--chart-grid-color', isDark ? '#374151' : '#e5e7eb');
}
/**
* Meta Theme-Color für Mobile Browser aktualisieren
*/
updateMetaThemeColor(isDark) {
let metaThemeColor = document.querySelector('meta[name="theme-color"]');
if (!metaThemeColor) {
metaThemeColor = document.createElement('meta');
metaThemeColor.name = 'theme-color';
document.head.appendChild(metaThemeColor);
}
metaThemeColor.content = isDark ? '#000000' : '#ffffff';
}
/**
* Theme-Synchronisation aus Storage
*/
syncThemeFromStorage() {
const storedTheme = localStorage.getItem(this.storageKey);
if (storedTheme !== null) {
const isDark = storedTheme === 'true';
if (this.isDarkMode() !== isDark) {
this.setTheme(isDark, false);
}
}
}
/**
* Toggle-Animation auslösen
*/
triggerToggleAnimation() {
// CSS-Klasse für Animation hinzufügen
this.htmlElement.classList.add('theme-transitioning');
// Animation nach kurzer Zeit entfernen
setTimeout(() => {
this.htmlElement.classList.remove('theme-transitioning');
}, 300);
}
/**
* Transition End Handler
*/
handleTransitionEnd(event) {
if (event.propertyName === 'background-color' && event.target === this.htmlElement) {
this.log('🎨 Theme-Transition abgeschlossen');
}
}
/**
* Custom Event dispatchen
*/
dispatchThemeEvent(isDark) {
const event = new CustomEvent('themeChanged', {
detail: {
theme: this.getTheme(),
isDark: isDark,
timestamp: Date.now()
}
});
document.dispatchEvent(event);
}
/**
* Callback registrieren
*/
onThemeChange(callback) {
if (typeof callback === 'function') {
this.callbacks.push(callback);
}
}
/**
* Callback entfernen
*/
offThemeChange(callback) {
const index = this.callbacks.indexOf(callback);
if (index > -1) {
this.callbacks.splice(index, 1);
}
}
/**
* Alle Callbacks ausführen
*/
executeCallbacks(isDark, previousTheme) {
this.callbacks.forEach(callback => {
try {
callback(isDark, previousTheme);
} catch (error) {
console.error('Theme Callback Fehler:', error);
}
});
}
/**
* Debug-Logging
*/
log(message) {
if (this.debug || localStorage.getItem('myp-theme-debug') === 'true') {
console.log(`[ThemeManager] ${message}`);
}
}
/**
* Debug-Modus aktivieren/deaktivieren
*/
setDebug(enabled) {
this.debug = enabled;
localStorage.setItem('myp-theme-debug', enabled.toString());
}
/**
* System-Präferenz abfragen
*/
getSystemPreference() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
/**
* Theme auf System-Präferenz zurücksetzen
*/
resetToSystemPreference() {
localStorage.removeItem(this.storageKey);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
this.setTheme(prefersDark, false);
this.log(`🔄 Theme auf System-Präferenz zurückgesetzt: ${prefersDark ? 'Dark' : 'Light'} Mode`);
}
/**
* Theme-Status für APIs
*/
getStatus() {
return {
currentTheme: this.getTheme(),
isDarkMode: this.isDarkMode(),
systemPreference: this.getSystemPreference(),
storageKey: this.storageKey,
toggleButtons: this.toggleButtons.length,
indicators: this.themeIndicators.length,
callbacks: this.callbacks.length
};
}
}
// ===== LEGACY COMPATIBILITY =====
// Globale Funktionen für Legacy-Kompatibilität
window.toggleDarkMode = function() {
if (window.themeManager) {
window.themeManager.toggle();
}
};
window.setDarkMode = function(enabled) {
if (window.themeManager) {
window.themeManager.setTheme(enabled);
}
};
window.isDarkMode = function() {
return window.themeManager ? window.themeManager.isDarkMode() : false;
};
// ===== AUTO-INITIALIZATION =====
// Theme Manager automatisch initialisieren wenn DOM bereit ist
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new UnifiedThemeManager();
});
} else {
new UnifiedThemeManager();
}
// ===== CSS TRANSITION SUPPORT =====
// CSS für sanfte Theme-Übergänge hinzufügen
const style = document.createElement('style');
style.textContent = `
.theme-transitioning,
.theme-transitioning * {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease !important;
}
.theme-toggle {
transition: transform 0.2s ease;
}
.theme-toggle:hover {
transform: scale(1.1);
}
.theme-toggle:active {
transform: scale(0.95);
}
`;
document.head.appendChild(style);
console.log('🎨 Unified Theme Manager geladen');
// Export für Module
if (typeof module !== 'undefined' && module.exports) {
module.exports = UnifiedThemeManager;
}