aiom_pack
Version:
Framework for interdependent (mcmc-like) behavioral experiments
417 lines (359 loc) • 18.6 kB
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 → 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(' → ')+'</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>