const contactPhotos = (() => {
const photoInput = document.getElementById('project-photos');
const photoList = document.getElementById('photo-list');
const photoStatus = document.getElementById('photos-status');
const formError = document.getElementById('form-error');
const maxPhotos = 5;
const maxPhotoBytes = 25 * 1024 * 1024;
const maxTotalPhotoBytes = 90 * 1024 * 1024;
let selectedPhotos = [];
function clearFormError() { formError.hidden = true; formError.textContent = ''; }
function formatPhotoStatus(files) {
if (!files.length) return '';
return files.length + ' of ' + maxPhotos + (files.length === 1 ? ' photo is' : ' photos are') + ' ready to send.';
}
function photoValidationMessage(files) {
var totalBytes = files.reduce(function (total, file) { return total + file.size; }, 0);
var oversizedPhoto = files.find(function (file) { return file.size > maxPhotoBytes; });
var nonPhoto = files.find(function (file) {
return file.type ? file.type.indexOf('image/') !== 0 : !/\.(jpe?g|png|gif|webp|heic|heif|avif|bmp|tiff?)$/i.test(file.name);
});
if (files.length > maxPhotos) {
return 'Choose no more than 5 photos. Remove one before adding another.';
}
if (oversizedPhoto) {
return oversizedPhoto.name + ' is larger than 25 MB and was not added.';
}
if (totalBytes > maxTotalPhotoBytes) {
return 'Keep the combined photo size under 90 MB. Those photos were not added.';
}
if (nonPhoto) return 'Choose image files only.';
return '';
}
function formatPhotoSize(bytes) {
if (bytes < 1024 * 1024) return Math.max(1, Math.round(bytes / 1024)) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1).replace('.0', '') + ' MB';
}
function photoKey(file) {
return [file.name, file.size, file.lastModified, file.type].join('|');
}
function renderPhotos(message) {
photoList.textContent = '';
selectedPhotos.forEach(function (file, index) {
var item = document.createElement('li');
var fileDetails = document.createElement('span');
var fileName = document.createElement('span');
var fileSize = document.createElement('span');
var removeButton = document.createElement('button');
item.className = 'photo-item';
fileDetails.className = 'photo-file';
fileName.className = 'photo-name';
fileName.textContent = file.name;
fileSize.className = 'photo-size';
fileSize.textContent = 'Photo ' + (index + 1) + ' ยท ' + formatPhotoSize(file.size);
removeButton.className = 'photo-remove';
removeButton.type = 'button';
removeButton.dataset.photoIndex = index;
removeButton.setAttribute('aria-label', 'Remove ' + file.name);
removeButton.textContent = 'Remove';
fileDetails.appendChild(fileName);
fileDetails.appendChild(fileSize);
item.appendChild(fileDetails);
item.appendChild(removeButton);
photoList.appendChild(item);
});
photoList.hidden = selectedPhotos.length === 0;
photoStatus.textContent = message || formatPhotoStatus(selectedPhotos);
}
photoInput.addEventListener('change', function () {
if (photoInput.disabled) return;
clearFormError();
var incomingPhotos = Array.prototype.slice.call(photoInput.files || []);
var selectedKeys = selectedPhotos.map(photoKey);
var duplicateCount = 0;
photoInput.value = '';
incomingPhotos = incomingPhotos.filter(function (file) {
var key = photoKey(file);
if (selectedKeys.indexOf(key) !== -1) {
duplicateCount += 1;
return false;
}
selectedKeys.push(key);
return true;
});
var candidatePhotos = selectedPhotos.concat(incomingPhotos);
var message = photoValidationMessage(candidatePhotos);
photoInput.setCustomValidity('');
if (message) {
renderPhotos(message);
return;
}
selectedPhotos = candidatePhotos;
renderPhotos(duplicateCount
? (duplicateCount === 1 ? 'That photo is already selected. ' : 'Those photos are already selected. ') + formatPhotoStatus(selectedPhotos)
: '');
});
photoList.addEventListener('click', function (e) {
var removeButton = e.target.closest('.photo-remove');
if (!removeButton || photoInput.disabled) return;
var index = Number(removeButton.dataset.photoIndex);
if (!Number.isInteger(index) || !selectedPhotos[index]) return;
selectedPhotos.splice(index, 1);
photoInput.setCustomValidity('');
clearFormError();
renderPhotos();
photoInput.focus();
});
return {
appendTo(data) {
data.delete(photoInput.name);
selectedPhotos.forEach(file => data.append(photoInput.name, file, file.name));
},
clear() { selectedPhotos = []; photoInput.value = ''; renderPhotos(); },
setDisabled(disabled) {
photoInput.disabled = disabled;
photoList.querySelectorAll('button').forEach(button => { button.disabled = disabled; });
}
};
})();