|
|
class Utils { |
|
|
static async loadJSON(filePath) { |
|
|
try { |
|
|
const response = await fetch(filePath); |
|
|
if (!response.ok) { |
|
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
|
} |
|
|
const data = await response.json(); |
|
|
return data; |
|
|
} catch (error) { |
|
|
console.error(`Error loading JSON from ${filePath}:`, error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
static saveToLocalStorage(key, data) { |
|
|
try { |
|
|
localStorage.setItem(key, JSON.stringify(data)); |
|
|
return true; |
|
|
} catch (error) { |
|
|
console.error('Error saving to localStorage:', error); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
static loadFromLocalStorage(key) { |
|
|
try { |
|
|
const data = localStorage.getItem(key); |
|
|
return data ? JSON.parse(data) : null; |
|
|
} catch (error) { |
|
|
console.error('Error loading from localStorage:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
static generateId() { |
|
|
return Date.now().toString(36) + Math.random().toString(36).substr(2); |
|
|
} |
|
|
|
|
|
static formatTime(seconds) { |
|
|
const minutes = Math.floor(seconds / 60); |
|
|
const remainingSeconds = seconds % 60; |
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; |
|
|
} |
|
|
|
|
|
static sanitizeHTML(str) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = str; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
static debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
|
|
|
static validateEmail(email) { |
|
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|
|
return re.test(email); |
|
|
} |
|
|
|
|
|
static validatePassword(password) { |
|
|
return password.length >= 6; |
|
|
} |
|
|
|
|
|
static calculatePercentage(part, total) { |
|
|
if (total === 0) return 0; |
|
|
return Math.round((part / total) * 100); |
|
|
} |
|
|
|
|
|
static getCurrentDate() { |
|
|
return new Date().toISOString().split('T')[0]; |
|
|
} |
|
|
|
|
|
static formatNumber(num) { |
|
|
return new Intl.NumberFormat('fa-IR').format(num); |
|
|
} |
|
|
|
|
|
static shuffleArray(array) { |
|
|
const shuffled = [...array]; |
|
|
for (let i = shuffled.length - 1; i > 0; i--) { |
|
|
const j = Math.floor(Math.random() * (i + 1)); |
|
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; |
|
|
} |
|
|
return shuffled; |
|
|
} |
|
|
|
|
|
static isMobileDevice() { |
|
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); |
|
|
} |
|
|
|
|
|
static async copyToClipboard(text) { |
|
|
try { |
|
|
await navigator.clipboard.writeText(text); |
|
|
return true; |
|
|
} catch (error) { |
|
|
console.error('Failed to copy text:', error); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
static showNotification(message, type = 'info') { |
|
|
const notification = document.createElement('div'); |
|
|
notification.className = `notification notification-${type}`; |
|
|
notification.innerHTML = ` |
|
|
<div class="notification-content"> |
|
|
<span class="notification-message">${this.sanitizeHTML(message)}</span> |
|
|
<button class="notification-close">×</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
notification.style.cssText = ` |
|
|
position: fixed; |
|
|
top: 20px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
background: ${type === 'error' ? '#fed7d7' : type === 'success' ? '#c6f6d5' : '#bee3f8'}; |
|
|
color: ${type === 'error' ? '#742a2a' : type === 'success' ? '#22543d' : '#2a4365'}; |
|
|
padding: 12px 20px; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
|
|
z-index: 1000; |
|
|
animation: slideDown 0.3s ease-out; |
|
|
`; |
|
|
|
|
|
document.body.appendChild(notification); |
|
|
|
|
|
notification.querySelector('.notification-close').addEventListener('click', () => { |
|
|
notification.remove(); |
|
|
}); |
|
|
|
|
|
setTimeout(() => { |
|
|
if (notification.parentNode) { |
|
|
notification.remove(); |
|
|
} |
|
|
}, 5000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const notificationStyles = ` |
|
|
@keyframes slideDown { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateX(-50%) translateY(-20px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateX(-50%) translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
.notification-content { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
gap: 15px; |
|
|
} |
|
|
|
|
|
.notification-close { |
|
|
background: none; |
|
|
border: none; |
|
|
font-size: 18px; |
|
|
cursor: pointer; |
|
|
padding: 0; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
`; |
|
|
|
|
|
|
|
|
if (!document.querySelector('#notification-styles')) { |
|
|
const styleElement = document.createElement('style'); |
|
|
styleElement.id = 'notification-styles'; |
|
|
styleElement.textContent = notificationStyles; |
|
|
document.head.appendChild(styleElement); |
|
|
} |