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>
|
<div class="icon">📷</div>
|
||||||
<p>Tap to choose a photo<br><strong>from your camera roll</strong></p>
|
<p>Tap to choose a photo<br><strong>from your camera roll</strong></p>
|
||||||
</div>
|
</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">
|
<img id="previewImg" class="preview-img" alt="preview">
|
||||||
<div id="progress1" class="progress-wrap">
|
<div id="progress1" class="progress-wrap">
|
||||||
<div class="progress-bar"><div class="progress-fill" id="fill1"></div></div>
|
<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() {
|
function uploadPhoto() {
|
||||||
document.getElementById('nextStep1').disabled=true;
|
var fileInput = document.getElementById('photoFile');
|
||||||
uploadFile(
|
var file = fileInput.files[0];
|
||||||
document.getElementById('photoFile'),
|
if (!file) return;
|
||||||
'/api/upload/photo',
|
|
||||||
document.getElementById('progress1'),
|
document.getElementById('nextStep1').disabled = true;
|
||||||
document.getElementById('fill1'),
|
var progressWrap = document.getElementById('progress1');
|
||||||
document.getElementById('txt1'),
|
var fill = document.getElementById('fill1');
|
||||||
function(filename){
|
var txt = document.getElementById('txt1');
|
||||||
if(filename){ uploadedPhoto=filename; goStep(2); }
|
|
||||||
else showToast('Photo upload failed. Try again.', false);
|
progressWrap.style.display = 'block';
|
||||||
document.getElementById('nextStep1').disabled=false;
|
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() {
|
function uploadAudio() {
|
||||||
|
|||||||
Reference in New Issue
Block a user