Implement browser-side image processing: convert any selected file to 172x320 PNG with cover aspect-ratio crop before upload
This commit is contained in:
@@ -132,7 +132,7 @@ input[type=range]{width:100%;accent-color:var(--accent)}
|
||||
<div class="icon">📷</div>
|
||||
<p>Tap to choose a photo<br><strong>from your camera roll</strong></p>
|
||||
</div>
|
||||
<input type="file" id="photoFile" accept=".png,.jpg,.jpeg" onchange="onPhotoSelected(this)">
|
||||
<input type="file" id="photoFile" accept="image/*" onchange="onPhotoSelected(this)">
|
||||
<img id="previewImg" class="preview-img" alt="preview">
|
||||
<div id="progress1" class="progress-wrap">
|
||||
<div class="progress-bar"><div class="progress-fill" id="fill1"></div></div>
|
||||
@@ -344,19 +344,105 @@ function uploadFile(fileInput, endpoint, progressWrap, fill, txt, onDone) {
|
||||
}
|
||||
|
||||
function uploadPhoto() {
|
||||
var fileInput = document.getElementById('photoFile');
|
||||
var file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
document.getElementById('nextStep1').disabled = true;
|
||||
uploadFile(
|
||||
document.getElementById('photoFile'),
|
||||
'/api/upload/photo',
|
||||
document.getElementById('progress1'),
|
||||
document.getElementById('fill1'),
|
||||
document.getElementById('txt1'),
|
||||
function(filename){
|
||||
if(filename){ uploadedPhoto=filename; goStep(2); }
|
||||
else showToast('Photo upload failed. Try again.', false);
|
||||
var progressWrap = document.getElementById('progress1');
|
||||
var fill = document.getElementById('fill1');
|
||||
var txt = document.getElementById('txt1');
|
||||
|
||||
progressWrap.style.display = 'block';
|
||||
txt.textContent = 'Preparing photo...';
|
||||
fill.style.width = '10%';
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = 172;
|
||||
canvas.height = 320;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Center crop cover math
|
||||
var srcWidth = img.width;
|
||||
var srcHeight = img.height;
|
||||
var ratio = Math.max(172 / srcWidth, 320 / srcHeight);
|
||||
var newWidth = srcWidth * ratio;
|
||||
var newHeight = srcHeight * ratio;
|
||||
var x = (172 - newWidth) / 2;
|
||||
var y = (320 - newHeight) / 2;
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 172, 320);
|
||||
ctx.drawImage(img, x, y, newWidth, newHeight);
|
||||
|
||||
canvas.toBlob(function(blob) {
|
||||
if (!blob) {
|
||||
showToast('Photo preparation failed.', false);
|
||||
document.getElementById('nextStep1').disabled = false;
|
||||
progressWrap.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
var origName = file.name;
|
||||
var lastDot = origName.lastIndexOf('.');
|
||||
var baseName = lastDot >= 0 ? origName.substring(0, lastDot) : 'photo';
|
||||
var newFilename = baseName + '.png';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('file', blob, newFilename);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/upload/photo');
|
||||
xhr.upload.onprogress = function(progressEvt) {
|
||||
if (progressEvt.lengthComputable) {
|
||||
var pct = Math.round(progressEvt.loaded / progressEvt.total * 100);
|
||||
fill.style.width = pct + '%';
|
||||
txt.textContent = 'Uploading photo... ' + pct + '%';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
progressWrap.style.display = 'none';
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
if (res.ok && res.filename) {
|
||||
uploadedPhoto = res.filename;
|
||||
goStep(2);
|
||||
} else {
|
||||
showToast('Photo upload failed: ' + (res.error || 'unknown error'), false);
|
||||
}
|
||||
} catch(err) {
|
||||
showToast('Photo upload failed.', false);
|
||||
}
|
||||
document.getElementById('nextStep1').disabled = false;
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
progressWrap.style.display = 'none';
|
||||
showToast('Photo upload failed.', false);
|
||||
document.getElementById('nextStep1').disabled = false;
|
||||
};
|
||||
|
||||
xhr.send(fd);
|
||||
}, 'image/png');
|
||||
};
|
||||
img.onerror = function() {
|
||||
showToast('Invalid image file.', false);
|
||||
document.getElementById('nextStep1').disabled = false;
|
||||
progressWrap.style.display = 'none';
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
reader.onerror = function() {
|
||||
showToast('Failed to read file.', false);
|
||||
document.getElementById('nextStep1').disabled = false;
|
||||
progressWrap.style.display = 'none';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function uploadAudio() {
|
||||
|
||||
Reference in New Issue
Block a user