Skip to content

fix: free-form cropping in image lightbox (#737)#739

Merged
shaoster merged 2 commits into
mainfrom
issue/737-free-form-crop
May 29, 2026
Merged

fix: free-form cropping in image lightbox (#737)#739
shaoster merged 2 commits into
mainfrom
issue/737-free-form-crop

Conversation

@shaoster
Copy link
Copy Markdown
Owner

Problem

Both crop UIs locked to a fixed aspect ratio with no free-form resize (#737). Root cause: react-easy-crop is a pan/zoom-over-a-fixed-aspect-frame cropper — it has no draggable free-form crop box at all, so omitting aspect merely reverted CropOverlay to the library's 4/3 default.

Fix

Replace react-easy-crop with react-advanced-cropper (native free-form resizable stencil).

  • CropOverlay (image lightbox): RectangleStencil with no aspectRatio → resizable in any direction. Crop coordinates (image px) are converted to the existing fraction-based ImageCrop; no backend change.
  • Glaze Import Tool (GlazeImportCropStage): migrated to the same library but kept square (aspectRatio={1}). The square swatch is intentional product design from the original feature (Workflow for creating public glaze types and glaze combinations from local test-tile images #146 — "rotatable square crop box", square WebP output feeding the OCR label pipeline), not a bug. The CropSquare/OCR/render pipeline is unchanged.

Scope note vs the issue: the import tool is deliberately not made free-form. Its square crop is load-bearing for the square swatch output and OCR detection. Making it free-form would be a separate OCR-pipeline rework, not a #737 bug fix.

Regression Tests

  • CropOverlay.test.tsx — asserts no fixed aspectRatio on the stencil and that a non-square crop round-trips through onSave with independent width/height.
  • GlazeImportCropStage.test.tsx — asserts the stencil stays locked to aspectRatio === 1 and crop geometry updates via onChange.
  • ImageLightbox.test.tsx — mock updated to react-advanced-cropper.

Verification

  • bazel test //web:web_test — 37/37 pass.
  • bazel build --config=lint //web/... — eslint + tsc clean.
  • bazel build //web:web_lib //web:web_prod_lib — dev and prod bundles resolve the new package.

Closes #737

Replace react-easy-crop with react-advanced-cropper. react-easy-crop is a
pan/zoom-over-a-fixed-aspect-frame cropper with no draggable free-form crop
box, so omitting `aspect` only reverted CropOverlay to the library's 4:3
default — it could never be free-form.

CropOverlay (image lightbox) now uses a RectangleStencil with no aspectRatio,
giving a resizable-in-any-direction crop box. Coordinates are read from the
cropper (image pixels) and converted to the existing fraction-based ImageCrop.

The Glaze Import Tool swatch crop stays square (1:1) — that is intentional
product design from the original feature (#146, "rotatable square crop box";
square WebP swatch output feeding the OCR label pipeline), not a bug. It is
migrated to the same library but keeps aspectRatio={1} and the CropSquare
model, so the OCR/render pipeline is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GlazeImportCropStage: replace the leftover react-easy-crop cropEditor
  reducer (crop/zoom were dead after the migration) with a single rotation
  useState.
- Skip the onChange commit + OCR-state reset when the clamped crop is
  unchanged, since react-advanced-cropper fires onChange continuously.
- Drop the no-op className="cropper" from both croppers (sizing is via style).
- Add a CropOverlay Storybook story that renders the real (unmocked)
  react-advanced-cropper as an integration check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shaoster shaoster merged commit 1bc38a1 into main May 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: crop UI locks to a fixed aspect ratio instead of free-form

1 participant