diff --git a/server/public/admin.html b/server/public/admin.html index 70f5df0..16dae4b 100644 --- a/server/public/admin.html +++ b/server/public/admin.html @@ -158,6 +158,7 @@ const cropState = { targetInput: null, hint: null, + objectUrl: '', naturalW: 0, naturalH: 0, scale: 1, @@ -207,6 +208,13 @@ const w = cropState.naturalW * cropState.scale const h = cropState.naturalH * cropState.scale + // Cover the crop window so zoom/pan never leaves it empty. + const minX = size.maskLeft + size.maskW - w + const maxX = size.maskLeft + const minY = size.maskTop + size.maskH - h + const maxY = size.maskTop + cropState.x = Math.min(maxX, Math.max(minX, cropState.x)) + cropState.y = Math.min(maxY, Math.max(minY, cropState.y)) img.style.width = `${w}px` img.style.height = `${h}px` img.style.left = `${cropState.x}px` @@ -250,31 +258,35 @@ function openCoverCrop(file, targetInput, hint) { const modal = document.getElementById('cropModal') const img = document.getElementById('cropImage') + if (cropState.objectUrl) URL.revokeObjectURL(cropState.objectUrl) cropState.targetInput = targetInput cropState.hint = hint - cropState.scale = 0 + cropState.scale = 1 cropState.x = 0 cropState.y = 0 - const url = URL.createObjectURL(file) + // Keep the blob URL alive until the dialog closes. Revoking it on load + // makes the image vanish the next time width/height change (zoom). + cropState.objectUrl = URL.createObjectURL(file) img.onload = () => { cropState.naturalW = img.naturalWidth cropState.naturalH = img.naturalHeight - URL.revokeObjectURL(url) modal.classList.add('on') requestAnimationFrame(() => { - if (!fitCropImage()) { - setTimeout(() => fitCropImage(), 50) - } + if (!fitCropImage()) setTimeout(() => fitCropImage(), 50) }) } - img.src = url + img.src = cropState.objectUrl } function closeCoverCrop() { document.getElementById('cropModal').classList.remove('on') - document.getElementById('cropImage').removeAttribute('src') + const img = document.getElementById('cropImage') + img.removeAttribute('src') + if (cropState.objectUrl) { + URL.revokeObjectURL(cropState.objectUrl) + cropState.objectUrl = '' + } cropState.targetInput = null - cropState.scale = 0 } function exportCroppedFile() {