**Ä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>
525 lines
16 KiB
JavaScript
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;
|
|
} |