Add all files
This commit is contained in:
376
themes/lucentui/js/app.js
Normal file
376
themes/lucentui/js/app.js
Normal file
@@ -0,0 +1,376 @@
|
||||
// Optimized app.js
|
||||
const passiveOptions = { passive: true };
|
||||
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.hook('request', ({ fail }) => {
|
||||
fail(({ status, preventDefault }) => {
|
||||
if (status === 419) {
|
||||
window.location.reload();
|
||||
preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Alpine.store('notifications', {
|
||||
notifications: [],
|
||||
|
||||
init() {
|
||||
Livewire.on('notify', (e) => {
|
||||
this.addNotification(e);
|
||||
});
|
||||
},
|
||||
|
||||
addNotification(notification) {
|
||||
notification = notification[0];
|
||||
notification.show = false;
|
||||
notification.id = Date.now() + Math.floor(Math.random() * 1000);
|
||||
this.notifications.push(notification);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.notifications = this.notifications.map(n => {
|
||||
if (n.id === notification.id) {
|
||||
n.show = true;
|
||||
}
|
||||
return n;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.removeNotification(notification.id);
|
||||
}, notification.timeout || 5000);
|
||||
},
|
||||
|
||||
removeNotification(id) {
|
||||
this.notifications = this.notifications.filter(n => {
|
||||
if (n.id === id) {
|
||||
n.show = false;
|
||||
}
|
||||
return n.show;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let timeUpdateInterval;
|
||||
const laravelLocale = document.documentElement.lang || 'en';
|
||||
|
||||
const localeMapping = {
|
||||
'ar': 'ar-SA', // Arabic (Saudi Arabia)
|
||||
'de': 'de-DE', // German (Germany)
|
||||
'en': 'en-US', // English (US)
|
||||
'es': 'es-ES', // Spanish (Spain)
|
||||
'fi': 'fi-FI', // Finnish (Finland)
|
||||
'fr': 'fr-FR', // French (France)
|
||||
'it': 'it-IT', // Italian (Italy)
|
||||
'sv': 'sv-SE', // Swedish (Sweden)
|
||||
'uk': 'uk-UA', // Ukrainian (Ukraine)
|
||||
'ko': 'ko-KR', // Korean (South Korea)
|
||||
'lv': 'lv-LV', // Latvian (Latvia)
|
||||
'nl': 'nl-NL', // Dutch (Netherlands)
|
||||
'no': 'no-NO', // Norwegian (Norway)
|
||||
'pt': 'pt-PT', // Portuguese (Portugal)
|
||||
'sr': 'sr-RS', // Serbian (Serbia)
|
||||
'id': 'id-ID' // Indonesian (Bahasa Indonesia)
|
||||
};
|
||||
|
||||
function updateDateTime() {
|
||||
const now = new Date();
|
||||
const timeElement = document.getElementById('current-time');
|
||||
const dateElement = document.getElementById('current-date');
|
||||
|
||||
const jsLocale = localeMapping[laravelLocale] || 'en-US'; // Default to 'id-ID' if not mapped
|
||||
|
||||
if (timeElement) {
|
||||
timeElement.textContent = now.toLocaleTimeString(jsLocale, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
if (dateElement) {
|
||||
dateElement.textContent = now.toLocaleDateString(jsLocale, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const debounce = (func, wait) => {
|
||||
let timeout;
|
||||
return (...args) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
};
|
||||
|
||||
const throttle = (func, limit) => {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let revealObserver;
|
||||
function initRevealAnimations() {
|
||||
const revealElements = document.querySelectorAll('.reveal-on-scroll');
|
||||
if (revealElements.length === 0) return;
|
||||
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '0px 0px -10% 0px',
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
revealObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.classList.add('revealed');
|
||||
revealObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
revealElements.forEach(el => {
|
||||
el.style.transform = 'translateY(20px)';
|
||||
el.style.opacity = '0';
|
||||
el.style.transition = 'transform 0.6s ease-out, opacity 0.6s ease-out';
|
||||
revealObserver.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
const buttonClickStates = new WeakMap();
|
||||
function preventDoubleClick(selector) {
|
||||
const buttons = document.querySelectorAll(selector);
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (buttonClickStates.has(button)) return;
|
||||
|
||||
const clickHandler = function(e) {
|
||||
if (buttonClickStates.get(button)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
buttonClickStates.set(button, true);
|
||||
button.classList.add('btn-loading');
|
||||
|
||||
const resetButton = () => {
|
||||
buttonClickStates.set(button, false);
|
||||
button.classList.remove('btn-loading');
|
||||
};
|
||||
|
||||
if (window.requestIdleCallback) {
|
||||
setTimeout(() => requestIdleCallback(resetButton), 2000);
|
||||
} else {
|
||||
setTimeout(resetButton, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
button.addEventListener('click', clickHandler, passiveOptions);
|
||||
buttonClickStates.set(button, false);
|
||||
});
|
||||
}
|
||||
|
||||
let imageObserver;
|
||||
function initLazyLoading() {
|
||||
const images = document.querySelectorAll('img[data-src]');
|
||||
if (images.length === 0) return;
|
||||
|
||||
imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src;
|
||||
img.classList.remove('lazy');
|
||||
img.removeAttribute('data-src');
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, { rootMargin: '50px' });
|
||||
|
||||
images.forEach(img => imageObserver.observe(img));
|
||||
}
|
||||
|
||||
let devicePerformanceLevel = null;
|
||||
function getDevicePerformance() {
|
||||
if (devicePerformanceLevel !== null) return devicePerformanceLevel;
|
||||
|
||||
const factors = [];
|
||||
|
||||
if (navigator.hardwareConcurrency) {
|
||||
factors.push(navigator.hardwareConcurrency >= 4 ? 1 : 0);
|
||||
}
|
||||
if (navigator.deviceMemory) {
|
||||
factors.push(navigator.deviceMemory >= 4 ? 1 : 0);
|
||||
}
|
||||
|
||||
if (navigator.connection) {
|
||||
const conn = navigator.connection;
|
||||
factors.push(conn.effectiveType === '4g' ? 1 : 0);
|
||||
}
|
||||
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
factors.push(isMobile ? 0 : 1);
|
||||
|
||||
const score = factors.reduce((a, b) => a + b, 0) / factors.length;
|
||||
devicePerformanceLevel = score > 0.5 ? 'high' : 'low';
|
||||
|
||||
return devicePerformanceLevel;
|
||||
}
|
||||
|
||||
function optimizeFormInteractions() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(form => {
|
||||
const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]');
|
||||
if (!submitBtn) return;
|
||||
|
||||
const submitHandler = function(e) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.classList.add('btn-loading');
|
||||
|
||||
setTimeout(() => {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.classList.remove('btn-loading');
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
form.addEventListener('submit', submitHandler, passiveOptions);
|
||||
});
|
||||
}
|
||||
|
||||
function optimizeForMobile() {
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
if (isMobile) {
|
||||
const viewport = document.querySelector('meta[name=viewport]');
|
||||
const inputs = document.querySelectorAll('input, select, textarea');
|
||||
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('focus', () => {
|
||||
if (viewport) {
|
||||
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0');
|
||||
}
|
||||
}, passiveOptions);
|
||||
|
||||
input.addEventListener('blur', () => {
|
||||
if (viewport) {
|
||||
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0');
|
||||
}
|
||||
}, passiveOptions);
|
||||
});
|
||||
|
||||
document.body.classList.add('mobile-optimized');
|
||||
}
|
||||
}
|
||||
|
||||
function setupErrorHandling() {
|
||||
window.addEventListener('error', (e) => {
|
||||
console.error('Error:', e.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
console.error('Unhandled promise rejection:', e.reason);
|
||||
});
|
||||
}
|
||||
|
||||
function showErrorNotification(message) {
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded shadow-lg z-50 transition-opacity duration-300';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
document.body.appendChild(errorDiv);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
errorDiv.style.opacity = '1';
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
errorDiv.style.opacity = '0';
|
||||
setTimeout(() => errorDiv.remove(), 300);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
function setupNetworkHandling() {
|
||||
let offlineNotification = null;
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
if (offlineNotification) return;
|
||||
|
||||
offlineNotification = document.createElement('div');
|
||||
offlineNotification.className = 'fixed top-4 right-4 bg-yellow-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
offlineNotification.textContent = 'You are offline. Some features may not work.';
|
||||
document.body.appendChild(offlineNotification);
|
||||
});
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
if (offlineNotification) {
|
||||
offlineNotification.remove();
|
||||
offlineNotification = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeApp() {
|
||||
updateDateTime();
|
||||
timeUpdateInterval = setInterval(updateDateTime, 1000);
|
||||
|
||||
const performance = getDevicePerformance();
|
||||
|
||||
if (performance === 'high') {
|
||||
initRevealAnimations();
|
||||
initLazyLoading();
|
||||
} else {
|
||||
document.body.classList.add('low-performance');
|
||||
document.querySelectorAll('.reveal-on-scroll').forEach(el => {
|
||||
el.classList.add('revealed');
|
||||
});
|
||||
}
|
||||
|
||||
preventDoubleClick('.btn-primary, [href*="checkout"], [href*="add-to-cart"]');
|
||||
optimizeFormInteractions();
|
||||
optimizeForMobile();
|
||||
setupErrorHandling();
|
||||
setupNetworkHandling();
|
||||
|
||||
if (performance === 'low') {
|
||||
const heavyElements = document.querySelectorAll('.animate-pulse-slowest, .animate-pulse-slow, .animate-pulse-fast');
|
||||
heavyElements.forEach(el => {
|
||||
el.style.animation = 'none';
|
||||
el.style.opacity = '0.1';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', () => {}, passiveOptions);
|
||||
document.addEventListener('touchmove', () => {}, passiveOptions);
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||
} else {
|
||||
initializeApp();
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (timeUpdateInterval) clearInterval(timeUpdateInterval);
|
||||
if (revealObserver) revealObserver.disconnect();
|
||||
if (imageObserver) imageObserver.disconnect();
|
||||
});
|
||||
|
||||
window.AppOptimizer = {
|
||||
debounce,
|
||||
throttle,
|
||||
getDevicePerformance,
|
||||
showErrorNotification
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user