🔧 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>
This commit is contained in:
525
backend/static/js/theme-manager-unified.js
Normal file
525
backend/static/js/theme-manager-unified.js
Normal file
@@ -0,0 +1,525 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
Reference in New Issue
Block a user