164 lines
6.0 KiB
JavaScript
164 lines
6.0 KiB
JavaScript
// ═══════════════════════════════════════════════════════
|
|
// QR Code Reader (using jsQR library)
|
|
// ═══════════════════════════════════════════════════════
|
|
let qrCameraStream = null;
|
|
let qrScanInterval = null;
|
|
let qrScanHistoryList = [];
|
|
|
|
function startQrCamera() {
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
return setStatus('qrReaderStatus', 'error', 'Camera API not supported in this browser.');
|
|
}
|
|
|
|
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
|
|
.then(stream => {
|
|
qrCameraStream = stream;
|
|
const video = document.getElementById('qrVideo');
|
|
video.srcObject = stream;
|
|
video.play();
|
|
|
|
document.getElementById('qrCameraContainer').style.display = 'block';
|
|
document.getElementById('qrCameraBtn').style.display = 'none';
|
|
document.getElementById('qrStopBtn').style.display = '';
|
|
document.getElementById('qrImagePreview').style.display = 'none';
|
|
|
|
setStatus('qrReaderStatus', 'info', 'Camera active — scanning for QR codes...');
|
|
|
|
// Start scanning frames
|
|
const canvas = document.getElementById('qrScanCanvas');
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
qrScanInterval = setInterval(() => {
|
|
if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
if (typeof jsQR !== 'undefined') {
|
|
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'dontInvert' });
|
|
if (code) {
|
|
showQrResult(code.data);
|
|
setStatus('qrReaderStatus', 'success', 'QR code detected ✓');
|
|
}
|
|
}
|
|
}
|
|
}, 250);
|
|
})
|
|
.catch(err => {
|
|
setStatus('qrReaderStatus', 'error', 'Camera access denied: ' + err.message);
|
|
});
|
|
}
|
|
|
|
function stopQrCamera() {
|
|
if (qrScanInterval) { clearInterval(qrScanInterval); qrScanInterval = null; }
|
|
if (qrCameraStream) {
|
|
qrCameraStream.getTracks().forEach(t => t.stop());
|
|
qrCameraStream = null;
|
|
}
|
|
const video = document.getElementById('qrVideo');
|
|
video.srcObject = null;
|
|
|
|
document.getElementById('qrCameraContainer').style.display = 'none';
|
|
document.getElementById('qrCameraBtn').style.display = '';
|
|
document.getElementById('qrStopBtn').style.display = 'none';
|
|
setStatus('qrReaderStatus', 'info', 'Camera stopped.');
|
|
}
|
|
|
|
function scanQrFromFile(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
// Stop camera if running
|
|
stopQrCamera();
|
|
|
|
const img = document.getElementById('qrPreviewImg');
|
|
const reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
img.src = e.target.result;
|
|
document.getElementById('qrImagePreview').style.display = 'block';
|
|
|
|
const tempImg = new Image();
|
|
tempImg.onload = function () {
|
|
const canvas = document.getElementById('qrImgCanvas');
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
canvas.width = tempImg.width;
|
|
canvas.height = tempImg.height;
|
|
ctx.drawImage(tempImg, 0, 0);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
if (typeof jsQR !== 'undefined') {
|
|
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'attemptBoth' });
|
|
if (code) {
|
|
showQrResult(code.data);
|
|
setStatus('qrReaderStatus', 'success', 'QR code detected in image ✓');
|
|
} else {
|
|
setStatus('qrReaderStatus', 'error', 'No QR code found in this image.');
|
|
}
|
|
} else {
|
|
setStatus('qrReaderStatus', 'error', 'QR scanning library not loaded.');
|
|
}
|
|
};
|
|
tempImg.src = e.target.result;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
|
|
// Reset file input so same file can be re-selected
|
|
event.target.value = '';
|
|
}
|
|
|
|
function showQrResult(data) {
|
|
document.getElementById('qrReaderPlaceholder').style.display = 'none';
|
|
document.getElementById('qrReaderResult').style.display = 'block';
|
|
document.getElementById('qrDecodedText').textContent = data;
|
|
|
|
// Detect type
|
|
let type = 'Plain Text';
|
|
const openBtn = document.getElementById('qrOpenLinkBtn');
|
|
openBtn.style.display = 'none';
|
|
|
|
if (/^https?:\/\//i.test(data)) {
|
|
type = '🔗 URL';
|
|
openBtn.style.display = '';
|
|
} else if (/^mailto:/i.test(data)) {
|
|
type = '📧 Email';
|
|
openBtn.style.display = '';
|
|
} else if (/^tel:/i.test(data)) {
|
|
type = '📞 Phone Number';
|
|
} else if (/^BEGIN:VCARD/i.test(data)) {
|
|
type = '👤 vCard Contact';
|
|
} else if (/^BEGIN:VEVENT/i.test(data)) {
|
|
type = '📅 Calendar Event';
|
|
} else if (/^WIFI:/i.test(data)) {
|
|
type = '📶 Wi-Fi Network';
|
|
} else if (/^smsto:/i.test(data)) {
|
|
type = '💬 SMS';
|
|
} else if (/^geo:/i.test(data)) {
|
|
type = '📍 Geolocation';
|
|
}
|
|
|
|
document.getElementById('qrDecodedType').textContent = type;
|
|
|
|
// Add to history (keep last 10)
|
|
const now = new Date().toLocaleTimeString();
|
|
qrScanHistoryList.unshift({ data: data.length > 80 ? data.slice(0, 80) + '...' : data, time: now, full: data });
|
|
if (qrScanHistoryList.length > 10) qrScanHistoryList.pop();
|
|
|
|
document.getElementById('qrScanHistory').innerHTML = qrScanHistoryList.map(h =>
|
|
`<div class="result-row" style="cursor:pointer;" onclick="copyText('${h.full.replace(/'/g, "\\'")}')">
|
|
<div class="label">${h.time}</div>
|
|
<div class="value" style="font-size:0.78rem;">${h.data.replace(/</g, '<')}</div>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
|
|
// Stop camera when navigating away
|
|
const origShowPage = window.showPage;
|
|
if (origShowPage) {
|
|
window.showPage = function (name) {
|
|
if (name !== 'qrreader' && qrCameraStream) stopQrCamera();
|
|
origShowPage(name);
|
|
};
|
|
}
|
|
|