Skip to content

Conversation

@rohan-pandeyy
Copy link
Contributor

@rohan-pandeyy rohan-pandeyy commented Dec 24, 2025

closes #656

Overview

Refactors the ImageViewer component by separating the zoom logic into a separate ZoomableImage component to implement a professional-grade image viewer experience. This update introduces "smart zoom" logic, refined panning logic, and robust centering behaviors to address imprecise/obscure zoom-ins & outs and unresponsive controls.

  1. Zooming on empty space centers the image.
  2. Zooming when "fit to screen" (overflow ie) switches to mouse anchored zooming.
  3. Panning is disabled when image fits.
  4. Image cannot be dragged completely off-screen.

Changes

  • Files Modified: frontend/src/components/Media/ZoomableImage.tsx, frontend/src/components/Media/ImageViewer.tsx
  • Logic: Replaced default library behaviors with custom wheel event interceptors and manual transform calculations.
  • State Management: Using useRef for performant access to transform state and useState for tracking overflow status.

Key Features

1. Smart Zoom Logic

  • Empty Space Zoom: Zooming while hovering over the empty space (black background) now keeps the image centered while zooming, rather than pulling the image towards the cursor.
  • Strict Centering (No Overflow): If the image fits entirely within the viewport, zooming in will always expand from the exact center, preventing accidental drift to corners.
  • Standard Focal Zoom (After Overflow): Switches to standard mouse-anchored/cursor-focused zooming when one of the edges (width/height) overflows from viewport.

2. Advanced Panning Logic

  • Loose Bounds: Implemented "loose" panning boundaries that allow the image to be dragged slightly off-screen (~20px buffer), ensuring the image is never lost.
  • No Inertia (1:1 Movement): Removed the previously put "momentum" effect from panning. The image now stops instantly when the mouse is released, providing precise control for inspection.
  • Conditional Panning: Panning is completely disabled when the image fits within the viewport, eliminating unnecessary cursor interactions.

3. UX Polish & Visuals

  • Dynamic Cursor:

    • default (Arrow): When image fits or cannot be panned.
    • move (Hand/Crosshair): Immediately appears when the image overflows and is pannable.
      image
  • Smooth Zoom-Out: Zooming out now interpolates towards the visual center of the viewport, eliminating the glitch where the image would drift away entirely out of viewport.

Video Demonstration

2025-12-24.15-36-48.mp4

Summary by CodeRabbit

  • New Features

    • Enhanced image viewer with improved zoom/pan controls, rotation support, programmatic zoom/reset, and robust image loading with fallback.
  • Bug Fixes

    • More predictable zoom behavior, centered resets, and tighter panning bounds for smoother interactions.
  • Chores

    • OpenAPI schema adjusted for the input_type parameter and image-cluster metadata now disallows arbitrary properties.

✏️ Tip: You can customize this high-level summary in your review settings.

refactored ImageViewer component to separate zoom logic from image viewing logic. Introduces 'smart zoom' logic, refined panning logic, and robust centering behaviors
@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

OpenAPI schema adjusted for input_type (wrapped in an allOf) and ImageInCluster.Metadata no longer allows arbitrary additional properties. Frontend extracts zoom/pan logic from ImageViewer into a new ZoomableImage component exposing imperative controls and improved wheel/overflow behavior.

Changes

Cohort / File(s) Summary
OpenAPI Schema Updates
docs/backend/backend_python/openapi.json
input_type parameter schema changed from "$ref": "#/components/schemas/InputType" to "allOf": [{ "$ref": "#/components/schemas/InputType" }] (title added); ImageInCluster.Metadata removed additionalProperties: true.
Frontend — ImageViewer refactor
frontend/src/components/Media/ImageViewer.tsx
Removed inline react-zoom-pan-pinch usage and convertFileSrc; forwards zoomIn/zoomOut/reset and rotation handling to new ZoomableImage via ref; removed reset useEffect.
Frontend — New ZoomableImage
frontend/src/components/Media/ZoomableImage.tsx
Added ZoomableImage + ZoomableImageRef (zoomIn/zoomOut/reset). Integrates react-zoom-pan-pinch, implements wheel-based zoom anchoring, dynamic fit-to-screen min scale, bounds-aware panning/clamping, rotation, load/error fallback, and overflow-aware panning toggles.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ImageViewer
  participant ZoomableImage
  participant TransformWrapper
  participant Image
  rect rgb(240,248,255)
    Note over User,ImageViewer: User interactions (wheel, drag, toolbar buttons)
  end
  User->>ImageViewer: wheel / drag / click zoom buttons
  ImageViewer->>ZoomableImage: call ref.zoomIn/zoomOut/reset or pass events
  ZoomableImage->>TransformWrapper: apply scale/translate (clamp bounds, center logic)
  TransformWrapper->>Image: render transformed image (rotation via CSS)
  Image-->>TransformWrapper: load / error events
  TransformWrapper-->>ZoomableImage: transform/state updates (overflow, limits)
  ZoomableImage-->>ImageViewer: optional callbacks (onReset/onError)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement, UI

Suggested reviewers

  • rahulharpal1603

Poem

🐰 I nudged the pixels, twirled them around,
I hop, I center, I keep them bound.
Zoom in, zoom out, a neat little spin,
Pan snug and steady — let the viewing begin! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes OpenAPI schema changes to docs/backend/backend_python/openapi.json that are unrelated to zoom/panning logic and frontend media viewer components. Remove the backend OpenAPI schema modifications as they are out of scope for this zoom/panning enhancement, or address them in a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main enhancement focus on advanced zoom and panning logic, which aligns with the primary code changes.
Linked Issues check ✅ Passed The PR successfully implements the key requirements from issue #656: dynamic fitScale, axis-dependent zoom anchoring, automatic re-centering, and bounds-aware panning.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa2a17 and c071193.

📒 Files selected for processing (1)
  • frontend/src/components/Media/ZoomableImage.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Backend Tests
🔇 Additional comments (9)
frontend/src/components/Media/ZoomableImage.tsx (9)

1-20: LGTM! Clean imports and sensible constants.

The imports are correct, and the constants align well with the PR objectives. MIN_SCALE=1 ensures users cannot zoom below the fit-to-screen size, addressing issue #656's requirement to prevent tiny/floating images. The PAN_PADDING=20 matches the described ~20px buffer for panning bounds.


22-49: LGTM! Well-defined types and interfaces.

The AnimationType union correctly matches the react-zoom-pan-pinch library's easing options. The ZoomableImageRef interface provides a clean imperative API for external control, and ZoomableImageProps includes all necessary configuration.


51-72: LGTM! Correct rotation handling and ref management.

The getEffectiveDimensions callback correctly handles 90°/270° rotations by swapping dimensions, which is essential for accurate overflow detection and bounds calculation with CSS-rotated images. The rotationRef pattern ensures callbacks always see current rotation values without dependency issues.


74-95: LGTM! Rotation-aware overflow detection.

The overflow calculation correctly accounts for image rotation by using getEffectiveDimensions to convert DOM dimensions to visual dimensions before comparing against the viewport. This ensures accurate overflow detection regardless of rotation angle.


97-141: LGTM! Robust reset with rotation support.

The handleReset function correctly computes fit-to-screen positioning with rotation awareness. It uses getEffectiveDimensions to calculate the proper aspect ratio, then applies contain-fit logic to center the image. The zero-dimension guard (line 113) prevents edge cases, and the preference for naturalWidth/Height over clientWidth/Height ensures accurate calculations.


143-184: LGTM! Proper imperative API and effect dependencies.

The useImperativeHandle correctly exposes the zoom/reset API. All effects have accurate dependency arrays:

  • Reset effect (lines 149-151) responds to resetSignal changes as intended
  • Rotation effect (lines 153-166) updates overflow state when rotation changes
  • Image load effect (lines 168-184) handles both pre-loaded and loading images with proper cleanup

186-317: LGTM! Well-designed smart zoom with correct math.

The wheel interceptor implements the "smart zoom" behavior from the PR objectives:

  • Zoom out (lines 232-262): Interpolates toward center using ratio = (newScale - 1) / (currentScale - 1) to prevent the image from drifting off-screen
  • No overflow (lines 265-280): Centers the zoom since the image fits within the viewport
  • Not over image (lines 283-300): Zooms from viewport center to avoid anchoring to empty space

For standard mouse-anchored zoom (overflow + cursor over image), the handler falls through without preventing default, delegating to the library. The ResizeObserver pattern efficiently caches wrapper dimensions, and all math includes proper safety checks (lines 250-251) for division by zero. Dependencies are correct, and cleanup is thorough.


319-403: LGTM! Proper panning bounds with rotation awareness.

The TransformWrapper configuration correctly implements the PR objectives:

  • velocityDisabled: true removes panning inertia for 1:1 movement (line 335)
  • panning.disabled tied to overflow state prevents panning when image fits (line 334)
  • Dynamic cursor reflects pannable state

The onPanning handler implements rotation-aware bounds using getEffectiveDimensions (line 371) to compute visual dimensions, then applies PAN_PADDING buffer (lines 375-378). This correctly allows ~20px of the image to move off-screen before clamping, matching the spec. The transform clamping (lines 400-402) uses duration=0 to avoid recursion issues.


405-442: LGTM! Clean rendering with proper error handling.

The JSX structure is well-organized:

  • Dynamic cursor styling (lines 410, 415, 435) reflects the pannable state consistently across wrapper, content, and image
  • CSS rotation transform (line 434) works correctly because the component uses getEffectiveDimensions for all logical dimension calculations
  • convertFileSrc handles Tauri file paths with appropriate fallback to placeholder
  • draggable={false} prevents browser drag from conflicting with custom pan behavior
  • Error handler ensures graceful degradation for missing images

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rohan-pandeyy
Copy link
Contributor Author

Hi @rahulharpal1603,
I'm opening this PR primarily to get any kind of feedback (and a CodeRabbit review). Currently the PR only supports "mouse wheel based" zooming.
I’ll be pushing a follow up commit soon to add proper trackpad / finger gesture support, but wanted to get review on the current interaction model first.
Please don't merge this PR just yet. Thanks :)

@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
frontend/src/components/Media/ImageViewer.tsx (1)

19-19: Consider using ZoomableImageRef type for the ref.

The ref is typed as ImageViewerRef, but ZoomableImage exports its own ZoomableImageRef interface. While both interfaces are structurally identical, using the actual exported type improves maintainability—if ZoomableImageRef changes in the future, TypeScript would catch the mismatch.

🔎 Suggested change
+import { ZoomableImage, ZoomableImageRef } from './ZoomableImage';
-import { ZoomableImage } from './ZoomableImage';
 ...
-    const zoomableImageRef = useRef<ImageViewerRef>(null);
+    const zoomableImageRef = useRef<ZoomableImageRef>(null);
frontend/src/components/Media/ZoomableImage.tsx (2)

34-74: Consider memoizing handleReset with useCallback.

The handleReset function is used as a dependency in the useEffect on line 82-84, but since it's not memoized, including it in the dependency array would cause the effect to re-run on every render. The current implementation omits it from dependencies, which will trigger an ESLint react-hooks/exhaustive-deps warning.

🔎 Suggested approach

Wrap handleReset in useCallback:

+import { useCallback } from 'react';
 ...
-    const handleReset = () => {
+    const handleReset = useCallback(() => {
       if (
         !transformRef.current?.instance?.wrapperComponent ||
         !imageRef.current
       )
         return;
       // ... rest of function
       setIsOverflowing(false);
-    };
+    }, []);

Then update the effect:

     useEffect(() => {
       handleReset();
-    }, [resetSignal]);
+    }, [resetSignal, handleReset]);

85-125: Consider extracting shared centering logic.

The centering calculation (lines 94-116) duplicates the logic in handleReset (lines 41-72). Extracting this into a shared helper function would improve maintainability and reduce the risk of the two implementations diverging.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 917daff and c16c62d.

📒 Files selected for processing (3)
  • docs/backend/backend_python/openapi.json
  • frontend/src/components/Media/ImageViewer.tsx
  • frontend/src/components/Media/ZoomableImage.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/Media/ImageViewer.tsx (1)
frontend/src/components/Media/ZoomableImage.tsx (1)
  • ZoomableImage (28-370)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
🔇 Additional comments (13)
docs/backend/backend_python/openapi.json (3)

1120-1128: Verify intent and client compatibility of input_type parameter schema change.

The input_type parameter has been refactored from a direct $ref to an allOf wrapper. While this is valid OpenAPI 3.1.0 syntax, it may affect code generation and API client behavior. Confirm that:

  1. The allOf wrapper is intentional and not a side effect of schema generation.
  2. Existing API clients or generated SDKs that depend on this parameter handle the schema change correctly.
  3. This change is necessary for the frontend zoom/pan feature described in the PR objectives, or if it's an incidental backend change.

2204-2213: Clarify metadata additionalProperties constraint removal in ImageInCluster.

The metadata field in ImageInCluster has been changed from a schema with explicit additionalProperties control to an implicit object type. By default in OpenAPI 3.1.0, objects allow additional properties unless explicitly set to false. This change may affect:

  1. API validation strictness on clients and servers.
  2. Documentation clarity for API consumers about what metadata fields are allowed.

Confirm whether this relaxation of validation is intentional, and ensure downstream consumers (including the frontend components in this PR) are aware of this schema evolution.


1-2927: Scope mismatch: Backend schema changes in PR focused on frontend components.

The PR objectives describe frontend enhancements (ZoomableImage component extraction, zoom/pan logic), but this file contains backend OpenAPI schema modifications. Clarify whether:

  1. These backend schema changes are dependencies for the frontend work, or
  2. The file selection is incidental, and the frontend changes (ZoomableImage.tsx, ImageViewer.tsx) are the primary focus.

If the schema changes are necessary, ensure they're justified in the PR description.

frontend/src/components/Media/ImageViewer.tsx (1)

17-37: Clean delegation pattern.

The refactoring effectively extracts zoom/pan complexity into ZoomableImage while preserving the public API of ImageViewer. The imperative handle correctly proxies all methods.

frontend/src/components/Media/ZoomableImage.tsx (9)

1-13: LGTM on imports.

The imports are appropriate for the component's functionality.


15-26: Well-defined interfaces.

The prop and ref interfaces clearly define the component's API contract.


76-80: LGTM on imperative handle.

The imperative methods are correctly exposed and safely handle potentially null refs.


127-145: LGTM on overflow calculation.

The overflow state calculation is correct and handles the null image ref case safely.


147-246: Empty dependency array may cause linting warnings.

The effect runs only on mount with [], but references getOverflowState which is recreated each render. This works correctly because:

  1. The refs (imageRef, transformRef) are stable
  2. getOverflowState only reads from imageRef.current

However, ESLint's react-hooks/exhaustive-deps rule will flag this. If you want to suppress the warning explicitly, consider adding a lint disable comment or memoizing getOverflowState with useCallback.


357-364: Rotation may cause incorrect centering and panning bounds.

The rotation is applied via CSS transform: rotate(${rotation}deg), but the centering and panning calculations use naturalWidth/naturalHeight and clientWidth/clientHeight. When the image is rotated 90° or 270°:

  • getBoundingClientRect() returns the rotated bounding box (swapped width/height)
  • But aspect ratio calculations in handleReset use naturalWidth/naturalHeight which don't change

This can cause the image to be incorrectly centered or have wrong panning limits when rotated.

Consider testing with 90° rotation to verify the zoom/pan behavior is acceptable, or account for rotation in the dimension calculations:

const isSwapped = rotation === 90 || rotation === 270;
const effectiveW = isSwapped ? baseH : baseW;
const effectiveH = isSwapped ? baseW : baseH;

284-331: Well-implemented panning boundary clamping.

The loose panning bounds (≈20px buffer) implementation is solid and prevents the image from being dragged completely off-screen. The clamping logic correctly handles all four edges.


346-365: Good error handling for image loading.

The onError handler correctly prevents an infinite loop by nullifying onerror before setting the fallback source. The draggable={false} and select-none class appropriately prevent default drag behavior that would interfere with custom panning.


372-372: Good practice setting displayName.

Setting displayName on forwardRef components improves debugging experience in React DevTools.

@github-actions github-actions bot added bug Something isn't working frontend labels Dec 24, 2025
Viewport-centered zoom now preserves existing pan offsets
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
frontend/src/components/Media/ZoomableImage.tsx (6)

29-29: Consider using React 19's ref-as-prop pattern.

React 19 allows ref as a regular prop, making forwardRef optional. While your current implementation works correctly, you could simplify by accepting ref directly in the props interface.

Modern React 19 pattern
-export const ZoomableImage = forwardRef<ZoomableImageRef, ZoomableImageProps>(
-  ({ imagePath, alt, rotation, resetSignal }, ref) => {
+interface ZoomableImagePropsWithRef extends ZoomableImageProps {
+  ref?: React.Ref<ZoomableImageRef>;
+}
+
+export const ZoomableImage = ({ imagePath, alt, rotation, resetSignal, ref }: ZoomableImagePropsWithRef) => {

Based on React 19 documentation provided.


47-47: Extract hardcoded scale to a named constant.

The reset scale value is hardcoded. Define it as a constant (e.g., RESET_SCALE) for better maintainability and clarity, especially if this value needs to align with minScale in the TransformWrapper config.

Proposed refactor

At the top of the component:

+const RESET_SCALE = 1;
+
 export const ZoomableImage = forwardRef<ZoomableImageRef, ZoomableImageProps>(
   ({ imagePath, alt, rotation, resetSignal }, ref) => {

Then use it:

-    const scale = 1;
+    const scale = RESET_SCALE;

147-154: Extract magic numbers to named constants.

Lines 148-149 and 154 contain magic numbers that should be defined as constants for better maintainability and clarity.

Proposed refactor

At the top of the component:

+const WHEEL_LINE_MODE_MULTIPLIER = 33;
+const ZOOM_FACTOR = 0.001;
+const MIN_SCALE = 1;
+const MAX_SCALE = 8;
+
 export const ZoomableImage = forwardRef<ZoomableImageRef, ZoomableImageProps>(

Then use them:

-    const multiplier = isLineMode ? 33 : 1;
-    const factor = 0.001;
+    const multiplier = isLineMode ? WHEEL_LINE_MODE_MULTIPLIER : 1;
+    const factor = ZOOM_FACTOR;
     
-    const newScale = Math.max(1, Math.min(8, currentScale + zoomChange));
+    const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, currentScale + zoomChange));

Also update TransformWrapper config on lines 253-254 to use the same constants.


267-284: Refactor duplicate overflow state logic.

The onTransformed and onZoom handlers contain identical overflow state calculation logic. Extract this to a helper function to reduce duplication.

Proposed refactor
+const updateOverflowState = (ref: ReactZoomPanPinchRef) => {
+  const scale = ref.state.scale;
+  const wrapper = ref.instance.wrapperComponent;
+  if (!wrapper) return;
+  const rect = wrapper.getBoundingClientRect();
+  const overflow = getOverflowState(scale, rect.width, rect.height);
+  setIsOverflowing(overflow.width || overflow.height);
+};
+
 onTransformed={(ref) => {
-  const scale = ref.state.scale;
-  const wrapper = ref.instance.wrapperComponent;
-  if (!wrapper) return;
-  const rect = wrapper.getBoundingClientRect();
-  const overflow = getOverflowState(scale, rect.width, rect.height);
-  setIsOverflowing(overflow.width || overflow.height);
+  updateOverflowState(ref);
 }}
 onZoom={(ref) => {
-  const scale = ref.state.scale;
-  const wrapper = ref.instance.wrapperComponent;
-  if (!wrapper) return;
-  const rect = wrapper.getBoundingClientRect();
-  const overflow = getOverflowState(scale, rect.width, rect.height);
-  setIsOverflowing(overflow.width || overflow.height);
+  updateOverflowState(ref);
 }}

304-307: Extract panning buffer to a named constant.

The 20px panning buffer is hardcoded. Define it as a constant (e.g., PANNING_BUFFER_PX) for better maintainability, especially if this value needs tuning.

Proposed refactor

At the top of the component:

+const PANNING_BUFFER_PX = 20;
+
 export const ZoomableImage = forwardRef<ZoomableImageRef, ZoomableImageProps>(

Then use it:

-    const limitLeft = -scaledW + 20;
-    const limitRight = viewW - 20;
-    const limitTop = -scaledH + 20;
-    const limitBottom = viewH - 20;
+    const limitLeft = -scaledW + PANNING_BUFFER_PX;
+    const limitRight = viewW - PANNING_BUFFER_PX;
+    const limitTop = -scaledH + PANNING_BUFFER_PX;
+    const limitBottom = viewH - PANNING_BUFFER_PX;

335-365: Consider extracting cursor logic to reduce duplication.

The cursor styling is set in three places (lines 339, 344, 364) based on isOverflowing. While this works correctly, extracting it to a variable would improve maintainability.

Proposed refactor
+const cursorStyle = isOverflowing ? 'move' : 'default';
+
 <TransformComponent
   wrapperStyle={{
     width: '100%',
     height: '100%',
     overflow: 'visible',
-    cursor: isOverflowing ? 'move' : 'default',
+    cursor: cursorStyle,
   }}
   contentStyle={{
     width: 'fit-content',
     height: 'fit-content',
-    cursor: isOverflowing ? 'move' : 'default',
+    cursor: cursorStyle,
   }}
 >
   <img
     ...
     style={{
       ...
-      cursor: isOverflowing ? 'move' : 'default',
+      cursor: cursorStyle,
     }}
   />
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c16c62d and 2fa2a17.

📒 Files selected for processing (2)
  • frontend/src/components/Media/ImageViewer.tsx
  • frontend/src/components/Media/ZoomableImage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/Media/ImageViewer.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Backend Tests
🔇 Additional comments (2)
frontend/src/components/Media/ZoomableImage.tsx (2)

86-106: LGTM! Clean effect handling.

Both effects are well-implemented:

  • Reset effect properly depends on resetSignal and handleReset
  • Image load effect correctly handles both already-loaded and loading states with proper cleanup

353-357: LGTM! Proper error handling.

The image error handler correctly sets onerror = null to prevent infinite loops before falling back to the placeholder. This is a best practice for handling image load failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Zoom-on-Scroll Behavior Does Not Match Expected Media Viewer UX

1 participant