const Auth = { init: async () => { let html = await fetch('/lib/pages/auth/index.html').then((response) => response.text()); app.innerHTML = html; Auth.qr.init(); document.getElementById("page-auth-form").addEventListener("submit", async function (event) { event.preventDefault(); Auth.submit(); }); }, submit: async () => { const uuid = document .getElementById("auth-forms-uuid") .value .replace(/^https?:\/\/sheep-service\.com\/?\?(uuid|hash)=/, ""); console.log(uuid); const URL = `${CONFIG.api}auth`; USER = await fetch(URL, { method: 'GET', headers: { "Content-Type": "application/json, text/plain, */*", "Authorization": uuid } }).then(response => { if (response.status === 401) { localStorage.removeItem("uuid"); Router.navigate(`/auth`); } else { Cloud.start(); localStorage.setItem("uuid", uuid); Router.navigate(`/`, false); } return response.json(); }); console.log("USER Info: ", USER); if (USER.possibilities.can_view_stand) { newMenuItems({ id: 'menu-stand', title: 'Графік стенду', icon: ``, href: '/stand' }); } if (USER.possibilities.can_view_schedule) { newMenuItems({ id: 'menu-schedule', title: 'Графіки зібрань', icon: ``, href: '/schedule' }); } if (USER.possibilities.can_view_sheeps) { newMenuItems({ id: 'menu-sheeps', title: 'Вісники', icon: ``, href: '/sheeps', hidden: true }); await Sheeps.sheeps_list.loadAPI(); } if (USER.possibilities.can_manager_territory) { newMenuItems({ id: 'menu-territory', title: 'Території', icon: ``, href: '/territory', hidden: true }); } newMenuItems({ id: 'menu-options', title: 'Опції', icon: ``, href: '/options' }); }, qr: { init: () => { const dialog = document.getElementById('scanner-dialog'); const openBtn = document.getElementById('open-scanner-btn'); const closeBtn = document.getElementById('close-scanner-btn'); const uuidInput = document.getElementById('auth-forms-uuid'); const video = document.getElementById('video'); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d', { willReadFrequently: true }); const loadingStatus = document.getElementById('loading-status'); let streamRef = null; let isScanning = false; // --- Логика управления сканером --- async function startCamera() { loadingStatus.style.display = 'block'; loadingStatus.innerText = "Запуск камери..."; isScanning = true; try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: "environment" } } }); streamRef = stream; video.srcObject = stream; video.setAttribute("playsinline", true); video.play(); video.addEventListener('loadedmetadata', () => { loadingStatus.style.display = 'none'; requestAnimationFrame(tick); }); } catch (err) { // Фолбек, если основная камера недоступна (например, на ПК) try { const fallbackStream = await navigator.mediaDevices.getUserMedia({ video: true }); streamRef = fallbackStream; video.srcObject = fallbackStream; video.play(); loadingStatus.style.display = 'none'; requestAnimationFrame(tick); } catch (fallbackErr) { loadingStatus.innerText = "Помилка доступу до камери"; } } } function stopCamera() { isScanning = false; if (streamRef) { streamRef.getTracks().forEach(track => track.stop()); streamRef = null; } video.srcObject = null; } function tick() { if (!isScanning) return; if (video.readyState === video.HAVE_ENOUGH_DATA) { canvas.height = video.videoHeight; canvas.width = video.videoWidth; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); if (code && code.data.trim() !== "") { // Успешно считали UUID uuidInput.value = code.data; Auth.submit(); // Закрываем модалку (событие close сработает автоматически) dialog.close(); return; } } requestAnimationFrame(tick); } // --- Слушатели событий модального окна --- // Открыть сканер openBtn.addEventListener('click', () => { dialog.showModal(); // Открывает как модальное окно (с бэкдропом) startCamera(); }); // Закрыть сканер по кнопке крестика closeBtn.addEventListener('click', () => { dialog.close(); }); // Важно: событие 'close' срабатывает всегда (и при клике на крестик, и при нажатии Esc) dialog.addEventListener('close', () => { stopCamera(); }); }, } }