UNPKG

aiom_pack

Version:

Framework for interdependent (mcmc-like) behavioral experiments

417 lines (359 loc) 18.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/png" href="/pkg-static/images/favicon.png"/> <title>File Upload</title> <link rel="stylesheet" href="/pkg-static/css/upload.css"> <script src="https://cdn.jsdelivr.net/npm/heic2any@0.0.3/dist/heic2any.min.js"></script> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script> </head> <body> <div id="instructions"> <div id="insContent"> <h3>Instruction</h3> <p id="instruction" style="line-height: 1.8;"></p> <hr style="border: 1px solid #ccc; margin-top: 100px; width: 100%;"> <div style="margin-top: 10px;"> <input type="checkbox" id="consent_face"/> <label for="consent_face">I agree that the data uploaded in this page can be shown in academic publications</label> <br> <button id="consentButton">Proceed</button><br> <!-- <button id="declineButton">Decline and leave</button> --> </div> </div> </div> <h1>Upload</h1> <p>Please allow the browser to use your webcam when it is asked.</p> <p id="example_prompt">An example:</p> <div style="text-align: center;"><img id='example_img' src='' alt="Example" style="width: 170px; height: auto;"></div> <p> <strong>Note: </strong> Please make sure your uploads follow the order: <br> <p id="categories_order" style="text-align: center;"> <strong>happy &#8594; sad</strong> </p><br> After uploading all pictures following the categories above, you will be able to click the "confirm" button to submit. </p> <!-- Tabs for switching between upload and webcam --> <div class="tabs"> <div class="tab active" data-tab="upload" id="tab1">Upload</div> <div class="tab" data-tab="webcam" id="tab2">Webcam (click to open)</div> </div> <!-- Upload Tab Content --> <div id="upload-tab" class="tab-content active"> <div class="upload-container"> <p>Drag and drop images here or</p> <input type="file" id="file-input" class="file-input" accept=".jpg,.jpeg,.png,.heic" multiple> <button class="upload-btn" id="select-btn">Select Files</button> </div> </div> <!-- Webcam Tab Content --> <div id="webcam-tab" class="tab-content"> <div class="webcam-container"> <video id="webcam" autoplay playsinline></video> <p id="category_instruction" style="font-size: 28px; color:rgb(94, 2, 181)">Take a photo for </p> <div class="webcam-controls"> <button class="capture-btn" id="capture-btn">Take Photo</button> <button class="upload-btn" id="switch-camera-btn">Switch Camera</button> </div> </div> </div> <div id="preview"></div> <div id="status"></div> <button class="upload-btn" id="upload-btn" style="display: none;">Confirm</button> <script> const local_pid = Cookies.get('pid'); const local_categories = JSON.parse(Cookies.get('classes')); const selectedFiles = []; // Webcam variables let stream = null; let facingMode = 'user'; // Start with front camera let webcamActive = false; function show_instruction() { return $.Deferred(function(deferred) { // Display the modal $('#instruction').load(`${ENV.instruction_text}`, function(response, status, xhr) { if (status == "error") { $("#instruction").html("Sorry, there was an error loading the instruction."); } }); $("#instructions").css("display", "flex"); // Wait for the user to click "Continue" $("#consentButton").on("click", function() { $("#instructions").css("display", "none"); // Hide the modal deferred.resolve(); // Continue the script }); $("#declineButton").on("click", function() { window.location.href = 'upload_finished'; }); }).promise(); } // Initialize webcam async function initializeWebcam() { try { if (stream) { stopWebcam(); } const constraints = { video: { facingMode: facingMode, width: { ideal: 480 }, height: { ideal: 480 } } }; stream = await navigator.mediaDevices.getUserMedia(constraints); $('#webcam')[0].srcObject = stream; $('.webcam-container').css('display', 'flex'); webcamActive = true; $('#status').html('Camera ready').css('color', 'green'); $('#category_instruction').html(`Now please take a photo for <strong>${local_categories[selectedFiles.length]}!</strong>`); } catch (error) { console.error('Error accessing the webcam:', error); $('#status').html('Could not access camera. Please allow camera access or use file upload instead.') .css('color', 'red'); } } // Stop webcam function stopWebcam() { if (stream) { stream.getTracks().forEach(track => track.stop()); $('#webcam')[0].srcObject = null; stream = null; } webcamActive = false; $('.webcam-container').css('display', 'none'); } async function handleFiles() { const files = $('#file-input')[0].files; if (files.length > 0) { $('#preview').empty(); for (let i = 0; i < files.length; i++) { let file = files[i]; if (file.type === 'image/heic' || file.name.toLowerCase().endsWith('.heic')) { $('#status').html('Converting HEIC image...'); try { // Convert HEIC to JPEG const jpegBlob = await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.8 }); // Create a new file from the converted blob file = new File([jpegBlob], file.name.replace('.heic', '.jpg'), { type: 'image/jpeg' }); $('#status').html('HEIC conversion complete'); } catch (error) { console.error('HEIC conversion error:', error); $('#status').html('Failed to convert HEIC file. Please try another format.') .css('color', 'red'); continue; } } // Validate file type if (!file.type.match('image/jpeg') && !file.type.match('image/png')) { $('#status').html('Please upload only JPG, JPEG or PNG files.') .css('color', 'red'); continue; } selectedFiles.push(file); } create_preview(); if (selectedFiles.length > 0) { $('#status').html(`${selectedFiles.length} file(s) selected`).css('color', 'green'); $('#upload-btn').show(); } } } function create_preview() { $('#preview').empty(); if (selectedFiles.length === local_categories.length) { $('#category_instruction').html(`All photos taken. You can now click "Confirm" to submit.`); } else { $('#category_instruction').html(`Now please take a photo for <strong>${local_categories[selectedFiles.length]}!</strong>`); } if (selectedFiles.length != local_categories.length) { $('#upload-btn').prop('disabled', true); } else { $('#upload-btn').prop('disabled', false); } $.each(selectedFiles, function(index, file) { // Create container for preview and delete button const $container = $('<div>').addClass('preview-container') .attr('data-index', index); // Create preview image const $img = $('<img>').addClass('preview-image'); $container.append($img); // only Create delete button for the last image if (index === selectedFiles.length - 1) { const $deleteBtn = $('<div>').addClass('delete-btn') .html('×') .on('click', function() { removeImage(index); }); $container.append($deleteBtn); } $('#preview').append($container); // Load image preview const reader = new FileReader(); reader.onload = function(e) { $img.attr('src', e.target.result); }; reader.readAsDataURL(file); }); } function removeImage(index) { // Remove the file from selectedFiles array selectedFiles.splice(index, 1); // Update the preview create_preview(); // Update status if (selectedFiles.length > 0) { $('#status').html(`${selectedFiles.length} file(s) selected`) .css('color', 'green'); $('#upload-btn').show(); } else { $('#status').html('No files selected'); $('#upload-btn').hide(); } } function endExperiment() { window.location.href = 'upload_finished'; } $(document).ready(function() { show_instruction(); if (ENV.production_mode === 'upload') { $('#tab2').hide(); $('#example_prompt').html('An example of the images you should upload:'); } else if (ENV.production_mode === 'webcam') { $('#tab1, #upload-tab').hide(); $('#example_prompt').html('<strong>Note: </strong>Please make sure the photo is taken from the front, with clear focus and good lighting. For example:'); } $('#example_img').attr('src', `${ENV.example_img}`); const categories_order = '<strong>'+local_categories.join(' &#8594; ')+'</strong>'; $('#categories_order').html(categories_order); // console.log("local_pid: ", local_pid); // Tab functionality $('.tab').on('click', function() { const tabId = $(this).data('tab'); // Update active tab $('.tab').removeClass('active'); $(this).addClass('active'); // Show corresponding content $('.tab-content').removeClass('active'); $(`#${tabId}-tab`).addClass('active'); // Initialize or stop webcam as needed if (tabId === 'webcam') { initializeWebcam(); } else if (webcamActive) { stopWebcam(); } }); // Switch between front and back cameras $('#switch-camera-btn').on('click', function() { facingMode = facingMode === 'user' ? 'environment' : 'user'; initializeWebcam(); }); // Capture photo from webcam $('#capture-btn').on('click', function() { // Create a canvas element to draw the video frame const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const video = $('#webcam')[0]; // Set canvas dimensions to match video dimensions canvas.width = video.videoWidth; canvas.height = video.videoHeight; // Draw the current video frame on the canvas context.drawImage(video, 0, 0, canvas.width, canvas.height); // Convert canvas to blob canvas.toBlob(function(blob) { // Create a file from the blob const fileName = `webcam_${new Date().toISOString().replace(/[:.]/g, '-')}.jpg`; const file = new File([blob], fileName, { type: 'image/jpeg' }); // Add to selected files selectedFiles.push(file); // Update preview create_preview(); // Update status $('#status').html(`${selectedFiles.length} file(s) selected`).css('color', 'green'); $('#upload-btn').show(); }, 'image/jpeg', 0.9); // JPEG format with 90% quality }); // Open file dialog when select button is clicked $('#select-btn').on('click', function() { $('#file-input').click(); }); // Handle file selection $('#file-input').on('change', async function() { await handleFiles(); }); // Drag and drop functionality $('.upload-container').on('dragover', function(e) { e.preventDefault(); $(this).css('backgroundColor', '#f7f9fa'); }); $('.upload-container').on('dragleave', function() { $(this).css('backgroundColor', ''); }); $('.upload-container').on('drop', async function(e) { e.preventDefault(); $(this).css('backgroundColor', ''); if (e.originalEvent.dataTransfer.files) { $('#file-input')[0].files = e.originalEvent.dataTransfer.files; await handleFiles(); } }); // Handle upload to server $('#upload-btn').on('click', function() { if (selectedFiles.length === 0) { $('#status').html('No files selected for upload'); return; } const consentPublications = $('#consent_face').is(':checked') ? 'true' : 'false'; // Create FormData to send files const formData = new FormData(); $.each(selectedFiles, function(index, file) { const extension = file.name.split('.').pop().toLowerCase(); const file_category = local_categories[index]; const newFilename = `${file_category}.${extension}`; const renamedFile = new File([file], newFilename, { type: file.type }); formData.append('files', renamedFile); }); axios.post('/api/upload_files', formData, { headers: { 'Consent-Publications': consentPublications, 'pid': local_pid }, onUploadProgress: (progressEvent) => { const percentComplete = Math.round((progressEvent.loaded / progressEvent.total) * 100); $('#status').html(`Uploading: ${percentComplete}%`); } }) .then(function(response) { $('#status').html(response.data.message).css('color', 'green'); // Stop webcam if active if (webcamActive) { stopWebcam(); } // Reset after successful upload setTimeout(function() { $('#preview').empty(); $('#status').html(''); $('#upload-btn').hide(); selectedFiles.length = 0; $('#file-input').val(''); endExperiment(); }, 2000); }) .catch(function(error) { console.error('Upload error:', error); $('#status').html('Upload failed. Please check the connection and try again. If it persists, please contact us.').css('color', 'red'); $('#status').append('<br><a href="upload_finished">Skip the uploading.</a>'); }); }); }); </script> </body> </html>