diff --git a/src/components/CropViewer.vue b/src/components/CropViewer.vue index ec3ee9f..480b53a 100644 --- a/src/components/CropViewer.vue +++ b/src/components/CropViewer.vue @@ -32,6 +32,11 @@ const overlayRef = ref(null) const img = ref(null) const imgUrl = ref(null) +// Set when the deskew blob fails to decode — corrupt result, OOM in the +// decoder, or unsupported format. We surface a small fallback UI so the +// user can navigate back to step 4 and retry instead of staring at a +// permanently blank canvas. +const loadError = ref(false) // Rotation in degrees. Crop fractions of the rotated bbox. const rotationDeg = ref(0) @@ -73,10 +78,14 @@ function persist() { function loadImage(url: string) { const el = new Image() el.onload = () => { + loadError.value = false img.value = el fitToContainer() redraw() } + el.onerror = () => { + loadError.value = true + } el.src = url } @@ -392,6 +401,11 @@ onMounted(() => { }) onUnmounted(() => { + // If the user navigated away mid-drag (e.g. tapped Back during a corner + // pull), the local refs hold the latest crop coords but `persist()` only + // fires on `pointerup`. Flush here so cache + store match what was on + // screen. + persist() resizeObs?.disconnect() if (imgUrl.value) URL.revokeObjectURL(imgUrl.value) }) @@ -500,6 +514,24 @@ function next() { @pointerup="onPointerUp" @pointercancel="onPointerUp" /> +
+

+ Failed to load the deskew result. +

+

+ The image blob couldn't be decoded. Re-run the + perspective correction in the previous step. +

+ +