manage-your-printer/static/js/offline-app.min.js
2025-06-04 10:03:22 +02:00

28 lines
10 KiB
JavaScript

class MYPApp{constructor(){this.isOffline=!navigator.onLine;this.registerTime=new Date().toISOString();this.darkMode=document.documentElement.classList.contains('dark');this.setupOfflineDetection();this.setupServiceWorker();this.setupLocalStorage();this.setupUI();this.setupThemeListeners();console.log(`MYP App initialisiert um ${this.registerTime}`);console.log(`Initiale Netzwerkverbindung:${navigator.onLine?'Online':'Offline'}`);console.log(`Aktueller Modus:${this.darkMode?'Dark Mode':'Light Mode'}`);}
setupOfflineDetection(){window.addEventListener('online',()=>{this.isOffline=false;document.body.classList.remove('offline-mode');console.log('Netzwerkverbindung wiederhergestellt!');this.syncOfflineData();window.dispatchEvent(new CustomEvent('networkStatusChange',{detail:{isOffline:false}}));});window.addEventListener('offline',()=>{this.isOffline=true;document.body.classList.add('offline-mode');console.log('Netzwerkverbindung verloren!');window.dispatchEvent(new CustomEvent('networkStatusChange',{detail:{isOffline:true}}));});if(this.isOffline){document.body.classList.add('offline-mode');}}
setupServiceWorker(){if('serviceWorker'in navigator){const swPath='/sw.js';navigator.serviceWorker.register(swPath,{scope:'/'}).then(registration=>{console.log('Service Worker erfolgreich registriert mit Scope:',registration.scope);if(registration.installing){console.log('Service Worker wird installiert');}else if(registration.waiting){console.log('Service Worker wartet auf Aktivierung');}else if(registration.active){console.log('Service Worker ist aktiv');}
registration.addEventListener('updatefound',()=>{const newWorker=registration.installing;newWorker.addEventListener('statechange',()=>{console.log(`Service Worker Status:${newWorker.state}`);if(newWorker.state==='activated'){this.fetchAndCacheAppData();}});});registration.update();}).catch(error=>{console.error('Service Worker Registrierung fehlgeschlagen:',error);this.setupLocalCache();});navigator.serviceWorker.addEventListener('controllerchange',()=>{console.log('Service Worker Controller hat gewechselt');});}else{console.warn('Service Worker werden von diesem Browser nicht unterstützt');this.setupLocalCache();}}
setupLocalCache(){console.log('Verwende lokalen Cache als Fallback');this.fetchAndCacheAppData();setInterval(()=>{if(navigator.onLine){this.fetchAndCacheAppData();}},15*60*1000);}
fetchAndCacheAppData(){if(!navigator.onLine)return;const endpoints=['/api/printers','/api/jobs','/api/schedule','/api/status'];for(const endpoint of endpoints){fetch(endpoint).then(response=>response.json()).then(data=>{localStorage.setItem(`cache_${endpoint}`,JSON.stringify({timestamp:new Date().getTime(),data:data}));console.log(`Daten für ${endpoint}gecached`);}).catch(error=>{console.error(`Fehler beim Cachen von ${endpoint}:`,error);});}}
setupLocalStorage(){if(!localStorage.getItem('offlineChanges')){localStorage.setItem('offlineChanges',JSON.stringify([]));}
if(!localStorage.getItem('appConfig')){const defaultConfig={theme:'system',notifications:true,dataSync:true,lastSync:null};localStorage.setItem('appConfig',JSON.stringify(defaultConfig));}
this.cleanupOldCache(7);}
cleanupOldCache(daysOld){const now=new Date().getTime();const maxAge=daysOld*24*60*60*1000;for(let i=0;i<localStorage.length;i++){const key=localStorage.key(i);if(key.startsWith('cache_')){try{const item=JSON.parse(localStorage.getItem(key));if(item&&item.timestamp&&(now-item.timestamp>maxAge)){localStorage.removeItem(key);console.log(`Alter Cache-Eintrag entfernt:${key}`);}}catch(e){console.error(`Fehler beim Verarbeiten von Cache-Eintrag ${key}:`,e);}}}}
syncOfflineData(){if(!navigator.onLine)return;const offlineChanges=JSON.parse(localStorage.getItem('offlineChanges')||'[]');if(offlineChanges.length===0){console.log('Keine Offline-Änderungen zu synchronisieren');return;}
console.log(`${offlineChanges.length}Offline-Änderungen werden synchronisiert...`);document.body.classList.add('syncing');const syncPromises=offlineChanges.map(change=>{return fetch(change.url,{method:change.method,headers:{'Content-Type':'application/json','X-Offline-Change':'true'},body:JSON.stringify(change.data)}).then(response=>{if(!response.ok){throw new Error(`Fehler ${response.status}:${response.statusText}`);}
return response.json();}).then(data=>{console.log(`Änderung erfolgreich synchronisiert:${change.url}`);return{success:true,change};}).catch(error=>{console.error(`Synchronisierung fehlgeschlagen für ${change.url}:`,error);return{success:false,change,error};});});Promise.all(syncPromises).then(results=>{const failedChanges=results.filter(result=>!result.success).map(result=>result.change);localStorage.setItem('offlineChanges',JSON.stringify(failedChanges));const appConfig=JSON.parse(localStorage.getItem('appConfig')||'{}');appConfig.lastSync=new Date().toISOString();localStorage.setItem('appConfig',JSON.stringify(appConfig));document.body.classList.remove('syncing');const syncEvent=new CustomEvent('offlineDataSynced',{detail:{total:offlineChanges.length,succeeded:offlineChanges.length-failedChanges.length,failed:failedChanges.length}});window.dispatchEvent(syncEvent);console.log(`Synchronisierung abgeschlossen:${offlineChanges.length-failedChanges.length}erfolgreich,${failedChanges.length}fehlgeschlagen`);});}
addOfflineChange(url,method,data){const offlineChanges=JSON.parse(localStorage.getItem('offlineChanges')||'[]');offlineChanges.push({id:Date.now().toString(36)+Math.random().toString(36).substr(2,5),url,method,data,timestamp:new Date().toISOString()});localStorage.setItem('offlineChanges',JSON.stringify(offlineChanges));console.log(`Offline-Änderung gespeichert:${method}${url}`);}
setupThemeListeners(){window.addEventListener('darkModeChanged',(e)=>{this.darkMode=e.detail.isDark;this.updateAppTheme();const appConfig=JSON.parse(localStorage.getItem('appConfig')||'{}');appConfig.theme=this.darkMode?'dark':'light';localStorage.setItem('appConfig',JSON.stringify(appConfig));console.log(`Theme geändert:${this.darkMode?'Dark Mode':'Light Mode'}`);});window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',(e)=>{const appConfig=JSON.parse(localStorage.getItem('appConfig')||'{}');if(appConfig.theme==='system'){this.darkMode=e.matches;this.updateAppTheme();console.log(`Systemthema geändert:${this.darkMode?'Dark Mode':'Light Mode'}`);}});}
updateAppTheme(){const metaThemeColor=document.getElementById('metaThemeColor')||document.querySelector('meta[name="theme-color"]');if(metaThemeColor){metaThemeColor.setAttribute('content',this.darkMode?'#0f172a':'#ffffff');}
this.updateUIForTheme();}
updateUIForTheme(){const offlineBanner=document.getElementById('offline-banner');const syncIndicator=document.getElementById('sync-indicator');if(offlineBanner){if(this.darkMode){offlineBanner.classList.add('dark-theme');offlineBanner.classList.remove('light-theme');}else{offlineBanner.classList.add('light-theme');offlineBanner.classList.remove('dark-theme');}}
if(syncIndicator){if(this.darkMode){syncIndicator.classList.add('dark-theme');syncIndicator.classList.remove('light-theme');}else{syncIndicator.classList.add('light-theme');syncIndicator.classList.remove('dark-theme');}}
}
setupUI(){if(!document.getElementById('offline-banner')){const banner=document.createElement('div');banner.id='offline-banner';banner.className=`hidden fixed top-0 left-0 right-0 bg-amber-500 dark:bg-amber-600 text-white text-center py-2 px-4 z-50 ${this.darkMode?'dark-theme':'light-theme'}`;banner.textContent='Sie sind offline. Einige Funktionen sind eingeschränkt.';document.body.prepend(banner);if(this.isOffline){banner.classList.remove('hidden');}
window.addEventListener('online',()=>banner.classList.add('hidden'));window.addEventListener('offline',()=>banner.classList.remove('hidden'));}
if(!document.getElementById('sync-indicator')){const indicator=document.createElement('div');indicator.id='sync-indicator';indicator.className=`hidden fixed bottom-4 right-4 bg-indigo-600 dark:bg-indigo-700 text-white text-sm rounded-full py-1 px-3 z-50 flex items-center ${this.darkMode?'dark-theme':'light-theme'}`;const spinnerSvg=document.createElementNS('http://www.w3.org/2000/svg','svg');spinnerSvg.setAttribute('class','animate-spin -ml-1 mr-2 h-4 w-4 text-white');spinnerSvg.setAttribute('fill','none');spinnerSvg.setAttribute('viewBox','0 0 24 24');const circle=document.createElementNS('http://www.w3.org/2000/svg','circle');circle.setAttribute('class','opacity-25');circle.setAttribute('cx','12');circle.setAttribute('cy','12');circle.setAttribute('r','10');circle.setAttribute('stroke','currentColor');circle.setAttribute('stroke-width','4');const path=document.createElementNS('http://www.w3.org/2000/svg','path');path.setAttribute('class','opacity-75');path.setAttribute('fill','currentColor');path.setAttribute('d','M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z');spinnerSvg.appendChild(circle);spinnerSvg.appendChild(path);const text=document.createElement('span');text.textContent='Synchronisiere...';indicator.appendChild(spinnerSvg);indicator.appendChild(text);document.body.appendChild(indicator);const observer=new MutationObserver((mutations)=>{mutations.forEach((mutation)=>{if(mutation.type==='attributes'&&mutation.attributeName==='class'){if(document.body.classList.contains('syncing')){indicator.classList.remove('hidden');}else{indicator.classList.add('hidden');}}});});observer.observe(document.body,{attributes:true});}
this.setupMobileOptimizations();}
setupMobileOptimizations(){if('serviceWorker'in navigator&&navigator.serviceWorker.controller){this.setupTouchFeedback();this.fixMobileViewportHeight();if('scrollRestoration'in history){history.scrollRestoration='manual';}}}
setupTouchFeedback(){document.addEventListener('touchstart',function(e){if(e.target.tagName==='BUTTON'||e.target.tagName==='A'||e.target.closest('button')||e.target.closest('a')){const element=e.target.tagName==='BUTTON'||e.target.tagName==='A'?e.target:(e.target.closest('button')||e.target.closest('a'));element.classList.add('touch-active');}},{passive:true});document.addEventListener('touchend',function(){const activeElements=document.querySelectorAll('.touch-active');activeElements.forEach(el=>el.classList.remove('touch-active'));},{passive:true});}
fixMobileViewportHeight(){const setViewportHeight=()=>{const vh=window.innerHeight*0.01;document.documentElement.style.setProperty('--vh',`${vh}px`);};setViewportHeight();window.addEventListener('resize',setViewportHeight);window.addEventListener('orientationchange',()=>{setTimeout(setViewportHeight,100);});}}
document.addEventListener('DOMContentLoaded',()=>{window.myp=new MYPApp();});