|
|
|
|
|
class QRGenerator { |
|
|
constructor() { |
|
|
this.currentType = 'url'; |
|
|
this.currentQRUrl = null; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
init() { |
|
|
this.bindEvents(); |
|
|
this.updateSizeDisplay(); |
|
|
this.setActiveTab('url'); |
|
|
} |
|
|
|
|
|
bindEvents() { |
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const type = e.currentTarget.dataset.type; |
|
|
this.setActiveTab(type); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const sizeSlider = document.getElementById('qr-size'); |
|
|
sizeSlider.addEventListener('input', () => { |
|
|
this.updateSizeDisplay(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('generate-btn').addEventListener('click', () => { |
|
|
this.generateQRCode(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('download-btn').addEventListener('click', () => { |
|
|
this.downloadQRCode(); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.bindInputEvents(); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
bindInputEvents() { |
|
|
|
|
|
|
|
|
document.getElementById('url-input').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('text-input').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('email-input').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('email-subject').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('email-body').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('phone-input').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('wifi-ssid').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('wifi-password').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('wifi-security').addEventListener('change', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('wifi-hidden').addEventListener('change', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('sms-number').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
document.getElementById('sms-message').addEventListener('input', () => { |
|
|
this.clearPreview(); |
|
|
}); |
|
|
} |
|
|
|
|
|
setActiveTab(type) { |
|
|
this.currentType = type; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => { |
|
|
btn.classList.remove('active'); |
|
|
}); |
|
|
document.querySelector(`[data-type="${type}"]`).classList.add('active'); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.content-form').forEach(form => { |
|
|
form.classList.remove('active'); |
|
|
}); |
|
|
document.getElementById(`${type}-form`).classList.add('active'); |
|
|
|
|
|
|
|
|
this.clearPreview(); |
|
|
} |
|
|
|
|
|
updateSizeDisplay() { |
|
|
const size = document.getElementById('qr-size').value; |
|
|
document.querySelector('.size-value').textContent = `${size}px`; |
|
|
} |
|
|
|
|
|
autoGenerate() { |
|
|
const content = this.getContentData(); |
|
|
if (content && content.trim()) { |
|
|
this.generateQRCode(); |
|
|
} else { |
|
|
this.clearPreview(); |
|
|
} |
|
|
} |
|
|
|
|
|
getContentData() { |
|
|
switch (this.currentType) { |
|
|
case 'url': |
|
|
return document.getElementById('url-input').value; |
|
|
|
|
|
case 'text': |
|
|
return document.getElementById('text-input').value; |
|
|
|
|
|
case 'email': |
|
|
const email = document.getElementById('email-input').value; |
|
|
const subject = document.getElementById('email-subject').value; |
|
|
const body = document.getElementById('email-body').value; |
|
|
|
|
|
if (!email) return ''; |
|
|
|
|
|
let mailto = `mailto:${email}`; |
|
|
const params = []; |
|
|
if (subject) params.push(`subject=${encodeURIComponent(subject)}`); |
|
|
if (body) params.push(`body=${encodeURIComponent(body)}`); |
|
|
if (params.length > 0) mailto += `?${params.join('&')}`; |
|
|
|
|
|
return mailto; |
|
|
|
|
|
case 'phone': |
|
|
const phone = document.getElementById('phone-input').value; |
|
|
return phone ? `tel:${phone}` : ''; |
|
|
|
|
|
case 'wifi': |
|
|
const ssid = document.getElementById('wifi-ssid').value; |
|
|
const password = document.getElementById('wifi-password').value; |
|
|
const security = document.getElementById('wifi-security').value; |
|
|
const hidden = document.getElementById('wifi-hidden').checked; |
|
|
|
|
|
if (!ssid) return ''; |
|
|
|
|
|
return `WIFI:T:${security};S:${ssid};P:${password};H:${hidden ? 'true' : 'false'};;`; |
|
|
|
|
|
case 'sms': |
|
|
const smsNumber = document.getElementById('sms-number').value; |
|
|
const smsMessage = document.getElementById('sms-message').value; |
|
|
|
|
|
if (!smsNumber) return ''; |
|
|
|
|
|
let sms = `sms:${smsNumber}`; |
|
|
if (smsMessage) sms += `?body=${encodeURIComponent(smsMessage)}`; |
|
|
|
|
|
return sms; |
|
|
|
|
|
default: |
|
|
return ''; |
|
|
} |
|
|
} |
|
|
|
|
|
async generateQRCode() { |
|
|
const content = this.getContentData(); |
|
|
|
|
|
if (!content || !content.trim()) { |
|
|
this.showToast('Please enter content to generate QR code', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.showLoading(true); |
|
|
|
|
|
try { |
|
|
const qrUrl = this.buildQRUrl(content); |
|
|
await this.displayQRCode(qrUrl); |
|
|
this.currentQRUrl = qrUrl; |
|
|
|
|
|
|
|
|
document.getElementById('download-btn').disabled = false; |
|
|
|
|
|
this.showToast('QR code generated successfully!', 'success'); |
|
|
} catch (error) { |
|
|
console.error('Error generating QR code:', error); |
|
|
this.showToast('Failed to generate QR code. Please try again.', 'error'); |
|
|
} finally { |
|
|
this.showLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
buildQRUrl(content) { |
|
|
const size = document.getElementById('qr-size').value; |
|
|
const fgColor = document.getElementById('fg-color').value.replace('#', ''); |
|
|
const bgColor = document.getElementById('bg-color').value.replace('#', ''); |
|
|
const errorCorrection = document.getElementById('error-correction').value; |
|
|
const format = document.getElementById('output-format').value; |
|
|
|
|
|
const params = new URLSearchParams({ |
|
|
cht: 'qr', |
|
|
chs: `${size}x${size}`, |
|
|
chl: content, |
|
|
choe: 'UTF-8', |
|
|
chld: `${errorCorrection}|2`, |
|
|
icqrf: fgColor, |
|
|
icqrb: bgColor |
|
|
}); |
|
|
|
|
|
if (format === 'svg') { |
|
|
params.append('chof', '.svg'); |
|
|
} |
|
|
|
|
|
return `https://image-charts.com/chart?${params.toString()}`; |
|
|
} |
|
|
|
|
|
async displayQRCode(url) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const img = new Image(); |
|
|
img.onload = () => { |
|
|
const preview = document.getElementById('qr-preview'); |
|
|
preview.innerHTML = ''; |
|
|
preview.appendChild(img); |
|
|
preview.classList.add('has-qr'); |
|
|
resolve(); |
|
|
}; |
|
|
img.onerror = () => { |
|
|
reject(new Error('Failed to load QR code image')); |
|
|
}; |
|
|
img.src = url; |
|
|
img.alt = 'Generated QR Code'; |
|
|
img.style.maxWidth = '100%'; |
|
|
img.style.height = 'auto'; |
|
|
}); |
|
|
} |
|
|
|
|
|
clearPreview() { |
|
|
const preview = document.getElementById('qr-preview'); |
|
|
preview.innerHTML = ` |
|
|
<div class="placeholder"> |
|
|
<i class="fas fa-qrcode"></i> |
|
|
<p>Enter content to generate QR code</p> |
|
|
</div> |
|
|
`; |
|
|
preview.classList.remove('has-qr'); |
|
|
|
|
|
|
|
|
document.getElementById('download-btn').disabled = true; |
|
|
|
|
|
this.currentQRUrl = null; |
|
|
} |
|
|
|
|
|
async downloadQRCode() { |
|
|
if (!this.currentQRUrl) { |
|
|
this.showToast('No QR code to download', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
const format = document.getElementById('output-format').value; |
|
|
const response = await fetch(this.currentQRUrl); |
|
|
const blob = await response.blob(); |
|
|
|
|
|
const url = window.URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `qrcode.${format}`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
window.URL.revokeObjectURL(url); |
|
|
|
|
|
this.showToast('QR code downloaded successfully!', 'success'); |
|
|
} catch (error) { |
|
|
console.error('Error downloading QR code:', error); |
|
|
this.showToast('Failed to download QR code', 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
showLoading(show) { |
|
|
const overlay = document.getElementById('loading-overlay'); |
|
|
if (show) { |
|
|
overlay.classList.add('show'); |
|
|
} else { |
|
|
overlay.classList.remove('show'); |
|
|
} |
|
|
} |
|
|
|
|
|
showToast(message, type = 'info') { |
|
|
const container = document.getElementById('toast-container'); |
|
|
const toast = document.createElement('div'); |
|
|
toast.className = `toast ${type}`; |
|
|
|
|
|
const icon = type === 'success' ? 'fas fa-check-circle' : |
|
|
type === 'error' ? 'fas fa-exclamation-circle' : |
|
|
'fas fa-info-circle'; |
|
|
|
|
|
toast.innerHTML = ` |
|
|
<i class="${icon}"></i> |
|
|
<span>${message}</span> |
|
|
`; |
|
|
|
|
|
container.appendChild(toast); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
if (toast.parentNode) { |
|
|
toast.parentNode.removeChild(toast); |
|
|
} |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
isValidUrl(string) { |
|
|
try { |
|
|
new URL(string); |
|
|
return true; |
|
|
} catch (_) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
isValidEmail(email) { |
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|
|
return emailRegex.test(email); |
|
|
} |
|
|
|
|
|
isValidPhone(phone) { |
|
|
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/; |
|
|
return phoneRegex.test(phone.replace(/[\s\-\(\)]/g, '')); |
|
|
} |
|
|
|
|
|
|
|
|
validateContent() { |
|
|
const content = this.getContentData(); |
|
|
|
|
|
switch (this.currentType) { |
|
|
case 'url': |
|
|
if (content && !this.isValidUrl(content)) { |
|
|
this.showToast('Please enter a valid URL', 'error'); |
|
|
return false; |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'email': |
|
|
const email = document.getElementById('email-input').value; |
|
|
if (email && !this.isValidEmail(email)) { |
|
|
this.showToast('Please enter a valid email address', 'error'); |
|
|
return false; |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'phone': |
|
|
const phone = document.getElementById('phone-input').value; |
|
|
if (phone && !this.isValidPhone(phone)) { |
|
|
this.showToast('Please enter a valid phone number', 'error'); |
|
|
return false; |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'sms': |
|
|
const smsPhone = document.getElementById('sms-number').value; |
|
|
if (smsPhone && !this.isValidPhone(smsPhone)) { |
|
|
this.showToast('Please enter a valid phone number', 'error'); |
|
|
return false; |
|
|
} |
|
|
break; |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function formatPhoneNumber(phone) { |
|
|
|
|
|
const cleaned = phone.replace(/[^\d\+]/g, ''); |
|
|
return cleaned; |
|
|
} |
|
|
|
|
|
function escapeHtml(text) { |
|
|
const map = { |
|
|
'&': '&', |
|
|
'<': '<', |
|
|
'>': '>', |
|
|
'"': '"', |
|
|
"'": ''' |
|
|
}; |
|
|
return text.replace(/[&<>"']/g, m => map[m]); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const qrGenerator = new QRGenerator(); |
|
|
|
|
|
|
|
|
const urlInput = document.getElementById('url-input'); |
|
|
if (urlInput) { |
|
|
urlInput.placeholder = 'https://example.com'; |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
|
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { |
|
|
e.preventDefault(); |
|
|
qrGenerator.generateQRCode(); |
|
|
} |
|
|
|
|
|
|
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'd') { |
|
|
e.preventDefault(); |
|
|
if (!document.getElementById('download-btn').disabled) { |
|
|
qrGenerator.downloadQRCode(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => { |
|
|
btn.addEventListener('keydown', (e) => { |
|
|
if (e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
btn.click(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.form-input, .form-textarea').forEach(input => { |
|
|
input.addEventListener('blur', () => { |
|
|
qrGenerator.validateContent(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const previewArea = document.getElementById('qr-preview'); |
|
|
previewArea.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
previewArea.style.borderColor = 'var(--primary-color)'; |
|
|
}); |
|
|
|
|
|
previewArea.addEventListener('dragleave', (e) => { |
|
|
e.preventDefault(); |
|
|
previewArea.style.borderColor = ''; |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('beforeprint', () => { |
|
|
document.body.classList.add('printing'); |
|
|
}); |
|
|
|
|
|
window.addEventListener('afterprint', () => { |
|
|
document.body.classList.remove('printing'); |
|
|
}); |
|
|
|
|
|
|
|
|
if ('serviceWorker' in navigator) { |
|
|
navigator.serviceWorker.register('/sw.js').catch(err => { |
|
|
console.log('Service worker registration failed:', err); |
|
|
}); |
|
|
} |
|
|
|
|
|
console.log('QR Generator initialized successfully!'); |
|
|
}); |
|
|
|