manage-your-printer/static/js/core-utilities-optimized.js
2025-06-04 10:03:22 +02:00

468 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* MYP Platform Core Utilities - Optimized Version
* Consolidates common functionality from multiple files
* Reduces redundancy and improves performance
*/
(function(window) {
'use strict';
// Single instance pattern for core utilities
const MYPCore = {
// Version for cache busting
version: '1.0.0',
// CSRF Token Management (replaces 22+ different implementations)
csrf: {
_token: null,
getToken() {
if (!this._token) {
// Try multiple sources in order of preference
this._token =
document.querySelector('meta[name="csrf-token"]')?.content ||
document.querySelector('input[name="csrf_token"]')?.value ||
this._getFromCookie('csrf_token') ||
'';
}
return this._token;
},
_getFromCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
},
clearCache() {
this._token = null;
}
},
// DOM Utilities with caching
dom: {
_cache: new Map(),
// Cached querySelector
get(selector, parent = document) {
const key = `${parent === document ? 'doc' : 'el'}_${selector}`;
if (!this._cache.has(key)) {
this._cache.set(key, parent.querySelector(selector));
}
return this._cache.get(key);
},
// Cached querySelectorAll
getAll(selector, parent = document) {
return parent.querySelectorAll(selector);
},
// Efficient HTML escaping
escapeHtml(text) {
const div = this._escapeDiv || (this._escapeDiv = document.createElement('div'));
div.textContent = text;
return div.innerHTML;
},
// Clear cache when DOM changes
clearCache() {
this._cache.clear();
},
// Efficient element creation
create(tag, attrs = {}, children = []) {
const el = document.createElement(tag);
Object.entries(attrs).forEach(([key, value]) => {
if (key === 'class') {
el.className = value;
} else if (key === 'style' && typeof value === 'object') {
Object.assign(el.style, value);
} else if (key.startsWith('data-')) {
el.dataset[key.slice(5)] = value;
} else {
el.setAttribute(key, value);
}
});
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(child);
}
});
return el;
}
},
// Unified Notification System (replaces 3 separate systems)
notify: {
_container: null,
_queue: [],
_activeToasts: new Map(),
_toastId: 0,
show(message, type = 'info', duration = 5000, options = {}) {
// Check for DND mode
if (window.dndManager?.isEnabled && !options.force) {
console.log(`[DND] Suppressed ${type} notification:`, message);
return null;
}
const id = `toast-${++this._toastId}`;
const toast = this._createToast(id, message, type, duration, options);
this._ensureContainer();
this._container.appendChild(toast);
// Animate in
requestAnimationFrame(() => {
toast.classList.add('show');
});
// Auto remove
if (duration > 0 && !options.persistent) {
const timeout = setTimeout(() => this.close(id), duration);
this._activeToasts.set(id, { element: toast, timeout });
}
return id;
},
_createToast(id, message, type, duration, options) {
const toast = MYPCore.dom.create('div', {
id,
class: `myp-toast myp-toast-${type}`,
role: 'alert',
'aria-live': 'polite'
});
const icon = this._getIcon(type);
const content = MYPCore.dom.create('div', { class: 'toast-content' }, [
MYPCore.dom.create('span', { class: 'toast-icon' }, [icon]),
MYPCore.dom.create('span', { class: 'toast-message' }, [message])
]);
const closeBtn = MYPCore.dom.create('button', {
class: 'toast-close',
'aria-label': 'Close notification',
onclick: () => this.close(id)
}, ['×']);
toast.appendChild(content);
toast.appendChild(closeBtn);
if (duration > 0 && !options.persistent) {
const progress = MYPCore.dom.create('div', { class: 'toast-progress' });
const bar = MYPCore.dom.create('div', {
class: 'toast-progress-bar',
style: { animationDuration: `${duration}ms` }
});
progress.appendChild(bar);
toast.appendChild(progress);
}
return toast;
},
_getIcon(type) {
const icons = {
success: '✓',
error: '✗',
warning: '⚠',
info: ''
};
return icons[type] || icons.info;
},
_ensureContainer() {
if (!this._container) {
this._container = MYPCore.dom.get('#myp-toast-container') ||
MYPCore.dom.create('div', { id: 'myp-toast-container', class: 'myp-toast-container' });
if (!this._container.parentNode) {
document.body.appendChild(this._container);
}
}
},
close(id) {
const toast = this._activeToasts.get(id);
if (toast) {
clearTimeout(toast.timeout);
toast.element.classList.remove('show');
setTimeout(() => {
toast.element.remove();
this._activeToasts.delete(id);
}, 300);
}
},
closeAll() {
this._activeToasts.forEach((_, id) => this.close(id));
}
},
// API Request Utilities
api: {
_cache: new Map(),
_pendingRequests: new Map(),
async request(url, options = {}) {
const config = {
...options,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': MYPCore.csrf.getToken(),
...options.headers
}
};
// Check cache for GET requests
if (options.method === 'GET' || !options.method) {
const cacheKey = this._getCacheKey(url, options);
const cached = this._cache.get(cacheKey);
if (cached && !options.noCache) {
const age = Date.now() - cached.timestamp;
const maxAge = options.cacheTime || 300000; // 5 minutes default
if (age < maxAge) {
return cached.data;
}
}
// Prevent duplicate requests
if (this._pendingRequests.has(cacheKey)) {
return this._pendingRequests.get(cacheKey);
}
}
// Make request
const requestPromise = fetch(url, config)
.then(async response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Cache successful GET requests
if (config.method === 'GET' || !config.method) {
const cacheKey = this._getCacheKey(url, options);
this._cache.set(cacheKey, {
data,
timestamp: Date.now()
});
this._pendingRequests.delete(cacheKey);
}
return data;
})
.catch(error => {
if (config.method === 'GET' || !config.method) {
this._pendingRequests.delete(this._getCacheKey(url, options));
}
throw error;
});
if (config.method === 'GET' || !config.method) {
this._pendingRequests.set(this._getCacheKey(url, options), requestPromise);
}
return requestPromise;
},
_getCacheKey(url, options) {
return `${url}_${JSON.stringify(options.params || {})}`;
},
clearCache(pattern) {
if (pattern) {
for (const [key] of this._cache) {
if (key.includes(pattern)) {
this._cache.delete(key);
}
}
} else {
this._cache.clear();
}
}
},
// Time/Date Utilities
time: {
formatAgo(timestamp) {
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
const now = new Date();
const seconds = Math.floor((now - date) / 1000);
const intervals = [
{ label: 'Jahr', seconds: 31536000, plural: 'Jahre' },
{ label: 'Monat', seconds: 2592000, plural: 'Monate' },
{ label: 'Woche', seconds: 604800, plural: 'Wochen' },
{ label: 'Tag', seconds: 86400, plural: 'Tage' },
{ label: 'Stunde', seconds: 3600, plural: 'Stunden' },
{ label: 'Minute', seconds: 60, plural: 'Minuten' }
];
for (const interval of intervals) {
const count = Math.floor(seconds / interval.seconds);
if (count >= 1) {
const label = count === 1 ? interval.label : interval.plural;
return `vor ${count} ${label}`;
}
}
return 'Gerade eben';
},
formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
const parts = [];
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
return parts.join(' ');
}
},
// Performance Utilities
perf: {
debounce(func, wait) {
let timeout;
return function debounced(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
},
throttle(func, limit) {
let inThrottle;
return function throttled(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
memoize(func, resolver) {
const cache = new Map();
return function memoized(...args) {
const key = resolver ? resolver(...args) : args[0];
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
cache.set(key, result);
return result;
};
}
},
// Event Management
events: {
_listeners: new Map(),
on(element, event, handler, options = {}) {
const key = `${element}_${event}`;
if (!this._listeners.has(key)) {
this._listeners.set(key, new Set());
}
this._listeners.get(key).add(handler);
element.addEventListener(event, handler, options);
// Return cleanup function
return () => this.off(element, event, handler);
},
off(element, event, handler) {
const key = `${element}_${event}`;
const handlers = this._listeners.get(key);
if (handlers) {
handlers.delete(handler);
if (handlers.size === 0) {
this._listeners.delete(key);
}
}
element.removeEventListener(event, handler);
},
once(element, event, handler, options = {}) {
const wrappedHandler = (e) => {
handler(e);
this.off(element, event, wrappedHandler);
};
return this.on(element, event, wrappedHandler, options);
}
},
// Storage Utilities
storage: {
get(key, defaultValue = null) {
try {
const item = localStorage.getItem(`myp_${key}`);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error('Storage get error:', e);
return defaultValue;
}
},
set(key, value) {
try {
localStorage.setItem(`myp_${key}`, JSON.stringify(value));
return true;
} catch (e) {
console.error('Storage set error:', e);
return false;
}
},
remove(key) {
localStorage.removeItem(`myp_${key}`);
},
clear(prefix = 'myp_') {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(prefix)) {
localStorage.removeItem(key);
}
});
}
}
};
// Global shortcuts for backwards compatibility
window.MYPCore = MYPCore;
// Legacy function mappings
window.showToast = (msg, type, duration) => MYPCore.notify.show(msg, type, duration);
window.showNotification = window.showToast;
window.showFlashMessage = window.showToast;
window.showSuccessMessage = (msg) => MYPCore.notify.show(msg, 'success');
window.showErrorMessage = (msg) => MYPCore.notify.show(msg, 'error');
window.showWarningMessage = (msg) => MYPCore.notify.show(msg, 'warning');
window.showInfoMessage = (msg) => MYPCore.notify.show(msg, 'info');
// Auto-initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
console.log('✅ MYP Core Utilities initialized');
});
} else {
console.log('✅ MYP Core Utilities initialized');
}
})(window);