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() {