37842-vm/assets/js/main.js
2026-01-26 18:23:06 +00:00

268 lines
9.6 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function() {
let html5QrCode;
const btnScanBarcode = document.getElementById('btnScanBarcode');
const btnTakePhoto = document.getElementById('btnTakePhoto');
const reader = document.getElementById('reader');
const aiLoading = document.getElementById('aiLoading');
const pName = document.getElementById('p_name');
const pCategory = document.getElementById('p_category');
const pQuantity = document.getElementById('p_quantity');
const pExpiration = document.getElementById('p_expiration');
function checkHttps() {
if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
alert("Camera access requires HTTPS. Please ensure you are using a secure connection.");
return false;
}
return true;
}
function stopScanner() {
return new Promise((resolve) => {
console.log("Stopping scanner...");
cleanupUI();
if (html5QrCode && html5QrCode.isScanning) {
html5QrCode.stop().then(() => {
console.log("Scanner stopped.");
reader.style.display = 'none';
resolve();
}).catch(err => {
console.error("Failed to stop scanner", err);
reader.style.display = 'none';
resolve();
});
} else {
console.log("Scanner was not active.");
reader.style.display = 'none';
resolve();
}
});
}
function cleanupUI() {
const capBtn = document.getElementById('btnCaptureFrame');
if (capBtn) capBtn.remove();
const stopBtn = document.getElementById('btnStopScanner');
if (stopBtn) stopBtn.remove();
const flash = document.querySelector('.shutter-flash');
if (flash) flash.classList.remove('active');
reader.classList.remove('camera-mode');
}
function addStopButton() {
if (!document.getElementById('btnStopScanner')) {
const stopBtn = document.createElement('button');
stopBtn.id = 'btnStopScanner';
stopBtn.innerText = "Stop Camera";
stopBtn.className = "btn btn-danger btn-sm w-100 mt-2";
stopBtn.onclick = stopScanner;
reader.after(stopBtn);
}
}
function triggerFlash() {
let flash = document.querySelector('.shutter-flash');
if (!flash) {
flash = document.createElement('div');
flash.className = 'shutter-flash';
reader.appendChild(flash);
}
flash.classList.add('active');
setTimeout(() => flash.classList.remove('active'), 150);
}
btnScanBarcode.addEventListener('click', function() {
if (!checkHttps()) return;
stopScanner().then(() => {
reader.style.display = 'block';
if (!html5QrCode) {
html5QrCode = new Html5Qrcode("reader");
}
addStopButton();
const config = {
fps: 10,
qrbox: (viewfinderWidth, viewfinderHeight) => {
return { width: viewfinderWidth * 0.8, height: viewfinderHeight * 0.4 };
}
};
html5QrCode.start(
{ facingMode: "environment" },
config,
(decodedText) => {
console.log(`Barcode detected: ${decodedText}`);
triggerFlash();
stopScanner().then(() => {
analyzeProduct({ barcode: decodedText });
});
}
).catch(err => {
console.error("Scanner start error:", err);
alert("Could not start camera. Please check permissions.");
reader.style.display = 'none';
cleanupUI();
});
});
});
btnTakePhoto.addEventListener('click', function() {
if (!checkHttps()) return;
stopScanner().then(() => {
reader.style.display = 'block';
reader.classList.add('camera-mode');
if (!html5QrCode) {
html5QrCode = new Html5Qrcode("reader");
}
addStopButton();
html5QrCode.start(
{ facingMode: "environment" },
{ fps: 10 },
() => { /* ignore QR scans in photo mode unless we want both */ }
).then(() => {
if (!document.getElementById('btnCaptureFrame')) {
const capBtn = document.createElement('button');
capBtn.id = 'btnCaptureFrame';
capBtn.innerHTML = '<i class="bi bi-camera-fill me-1"></i> Take Photo & Identify';
capBtn.className = "btn btn-primary btn-lg w-100 mt-2 mb-1 py-3";
capBtn.onclick = capturePhoto;
// Insert before stop button if exists
const stopBtn = document.getElementById('btnStopScanner');
if (stopBtn) {
stopBtn.before(capBtn);
} else {
reader.after(capBtn);
}
}
}).catch(err => {
console.error("Camera start error:", err);
alert("Camera error: " + err);
reader.style.display = 'none';
cleanupUI();
});
});
});
function capturePhoto() {
console.log("Capturing photo...");
const video = document.querySelector('#reader video');
if (!video) {
console.error("Video element not found in reader");
alert("Camera not ready. Video element missing.");
return;
}
triggerFlash();
const canvas = document.getElementById('photoCanvas');
if (!canvas) {
console.error("Canvas element #photoCanvas not found");
alert("Application error: missing canvas.");
return;
}
const MAX_DIM = 1024;
let width = video.videoWidth;
let height = video.videoHeight;
if (width === 0 || height === 0) {
console.error("Video dimensions are 0", {width, height});
alert("Camera error: invalid video dimensions. Try moving the camera.");
return;
}
if (width > height) {
if (width > MAX_DIM) {
height *= MAX_DIM / width;
width = MAX_DIM;
}
} else {
if (height > MAX_DIM) {
width *= MAX_DIM / height;
height = MAX_DIM;
}
}
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, width, height);
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
console.log("Data URL generated, length:", dataUrl.length);
if (dataUrl.length < 1000) {
console.error("Data URL seems too short, capture might have failed");
}
setTimeout(() => {
console.log("Proceeding to identification...");
stopScanner().then(() => {
analyzeProduct({ image: dataUrl });
});
}, 300);
}
function analyzeProduct(params) {
console.log("Sending request to api/analyze.php", params.barcode ? "with barcode" : "with image");
aiLoading.style.display = 'block';
pName.placeholder = "Identifying...";
// Clear previous values to show something is happening
pName.value = '';
fetch('api/analyze.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
})
.then(res => {
console.log("Received response status:", res.status);
return res.json();
})
.then(res => {
console.log("Received data:", res);
aiLoading.style.display = 'none';
pName.placeholder = "e.g. Milk, Eggs, Bread";
if (res.success && res.data) {
const data = res.data;
if (data.name) pName.value = data.name;
if (data.category) pCategory.value = data.category;
if (data.quantity) pQuantity.value = data.quantity;
if (data.expiration_date) pExpiration.value = data.expiration_date;
// Visual highlight of changed fields
[pName, pCategory, pQuantity, pExpiration].forEach(el => {
if (el.value) {
el.classList.add('is-valid');
setTimeout(() => el.classList.remove('is-valid'), 2000);
}
});
} else {
console.error("Identification failed:", res.error);
alert("Could not identify the product: " + (res.error || "Please try again."));
}
})
.catch(err => {
aiLoading.style.display = 'none';
pName.placeholder = "e.g. Milk, Eggs, Bread";
console.error("Fetch error:", err);
alert("Analysis failed. Connection error or server issue.");
});
}
const addItemModal = document.getElementById('addItemModal');
if (addItemModal) {
addItemModal.addEventListener('hidden.bs.modal', function () {
stopScanner();
});
}
});