fix(crop): persist on unmount + handle image-load failure
Some checks failed
Deploy to GitHub Pages / build (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled

Two follow-up fixes flagged by the post-merge review:

- persist() on `onUnmounted` so a mid-drag navigation (Back button,
  hot-reload) doesn't lose the in-progress crop. The local refs
  already hold the latest coords; we just need to flush them through
  to the store + cache before the component goes away.
- Add `el.onerror` to the image load and a small fallback UI offering
  Back-to-Deskew. Without it, a corrupt deskew blob would leave the
  canvas permanently blank with no way to recover short of refresh.
This commit is contained in:
Samuel Prevost 2026-05-01 00:14:20 +02:00
parent 565baddfbf
commit 9f54bc62bd

View File

@ -32,6 +32,11 @@ const overlayRef = ref<HTMLCanvasElement | null>(null)
const img = ref<HTMLImageElement | null>(null)
const imgUrl = ref<string | null>(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"
/>
<div
v-if="loadError"
class="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-background/90 p-6 text-center"
>
<p class="text-sm font-medium text-destructive">
Failed to load the deskew result.
</p>
<p class="text-xs text-muted-foreground">
The image blob couldn't be decoded. Re-run the
perspective correction in the previous step.
</p>
<Button
variant="outline"
size="sm"
@click="store.goToStep(4)"
>Back to Deskew</Button
>
</div>
</div>
</CardContent>
</Card>