Skip to content

[PE-102] fix: zooming for fullscreen images#6266

Merged
pushya22 merged 5 commits intopreviewfrom
fix/image-fullscreen
Dec 23, 2024
Merged

[PE-102] fix: zooming for fullscreen images#6266
pushya22 merged 5 commits intopreviewfrom
fix/image-fullscreen

Conversation

@Palanikannan1437
Copy link
Member

@Palanikannan1437 Palanikannan1437 commented Dec 23, 2024

Description

Adds in more zooming for small images, and also allows scroll zooming and moving around while dragging the image.

Summary by CodeRabbit

  • New Features

    • Enhanced zooming and dragging capabilities for images in full-screen mode, improving user interaction.
    • Added pinch-to-zoom functionality for a more intuitive experience.
  • Bug Fixes

    • Improved handling of image magnification adjustments and state management during dragging.
  • Refactor

    • Organized import statements for better readability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2024

Walkthrough

This pull request modifies the image handling components in the editor. It reorganizes the import statements in image-node.tsx and enhances the ImageFullScreenAction component in full-screen.tsx by introducing new zoom constants and state management for improved zoom and drag functionalities. The changes also include updated event handling for mouse and wheel interactions, streamlining the user experience when interacting with images in full-screen mode.

Changes

File Change Summary
packages/editor/src/core/extensions/custom-image/components/image-node.tsx Reordered React hook imports without functional changes
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx - Added zoom constants (MIN_ZOOM, MAX_ZOOM, ZOOM_SPEED, ZOOM_STEPS)
- Introduced new state variables initialMagnification and isDragging
- Enhanced zoom and drag functionality
- Improved mouse and wheel event handling

Possibly related PRs

Suggested labels

✨feature, 🌐frontend, ✍️editor

Suggested reviewers

  • SatishGandham

Poem

🐰 A rabbit's tale of zoom and might,
Where images dance with pixel's delight.
Pinch and scroll, drag with grace,
Full-screen magic in coding's embrace.
Magnification's playful art! 🖼️


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Palanikannan1437 Palanikannan1437 self-assigned this Dec 23, 2024
@Palanikannan1437 Palanikannan1437 marked this pull request as ready for review December 23, 2024 14:11
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 (7)
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (7)

17-20: Use constants as configuration for flexible updates later
Declaring the zoom-related constants in one place is valuable for maintainability and clarity. If these values might change in the future or need to be exposed for theme/config overrides, consider centralizing them in a separate config or constants file shared across the codebase.


26-28: Consider storing magnification or position in a ref
Using a state variable for magnification forces React re-renders on every zoom adjustment. If performance becomes a concern, switching magnification handling to a ref may reduce unnecessary renders.


61-64: Allow closing even while dragging
Currently, if the user is dragging (isDragging == true), handleClose returns early. This might frustrate users who want to quickly close fullscreen mode while an accidental drag is active. Consider confirming whether preventing closure under drag is intentional.


66-85: Ensure consistent zoom experience
Using discrete zoom steps (ZOOM_STEPS) is intuitive for keyboard-based zoom adjustments. If you foresee a scenario where a user wants finer granularity—especially if the image is high resolution— you might consider a fallback continuous zoom approach or more steps.


101-120: Smooth transitions when dragging
While using "position: relative" and direct style changes is straightforward, it might produce abrupt movements if the user drags quickly. If a smooth user experience is desired, consider adding a slight transition or an easing function—though that must be weighed against making the drag feel “laggy.”


209-220: Animate left and top for a smoother reset
The CSS transition is only applied to width and transform. If you want a smooth return-to-center effect for the image, consider adding transitions for left/top as well.

- transform: `scale(${magnification})`,
- transition: "width 0.2s ease, transform 0.2s ease",
+ transform: `scale(${magnification})`,
+ transition: "width 0.2s ease, transform 0.2s ease, left 0.2s ease, top 0.2s ease",

224-243: Keyboard accessibility for zoom controls
The minus/plus buttons are only clickable with mouse or pointer. Consider adding keyboard navigation shortcuts or ARIA attributes to improve accessibility for these zoom controls.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88737b1 and af0cf97.

📒 Files selected for processing (2)
  • packages/editor/src/core/extensions/custom-image/components/image-node.tsx (1 hunks)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/editor/src/core/extensions/custom-image/components/image-node.tsx
🔇 Additional comments (4)
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (4)

143-164: Pinch-to-zoom logic is well-structured
The approach is clean and straightforward. The clamp logic prevents overshooting the zoom limits. Keep an eye out for future requests to handle multi-finger gestures on touch devices.


190-200: Ensure background clicks are intended
You’re closing the fullscreen when the user clicks on the modal background. This is a common pattern, but be mindful if users might expect a different interaction (e.g., possibly re-centering their image). So far, it looks appropriate for a typical fullscreen experience.

✅ Verification successful

Let me verify the interaction pattern by checking if there are similar patterns in the codebase or any specific requirements defined.

This will help us understand:

  1. If this background click-to-close pattern is consistently used across the codebase
  2. If there are any documented requirements or guidelines about modal/fullscreen interactions

Let me search for more modal-related patterns to understand if this interaction is consistent with other modal implementations in the codebase.

This will help us understand:

  1. How other modal components handle background interactions
  2. If there are other fullscreen implementations with similar patterns

Let me try one more search with corrected file type.

This will help us understand:

  1. The modal interaction patterns across the codebase
  2. Any documented requirements for image interactions

Since we've been unable to find definitive patterns in the codebase about modal interactions or specific documentation about fullscreen image behavior, I'll generate the final response based on the code review context and common UX patterns.

Background click behavior follows standard modal pattern
The current implementation of closing the fullscreen view on background click follows a widely accepted modal interaction pattern. Since this is a fullscreen image viewer, clicking the background (away from the image) is an intuitive way to exit the view, and it's unlikely to conflict with other expected interactions like image re-centering, which should be handled by clicking and dragging the image itself.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Search for similar modal/fullscreen background click handlers
rg -U "onMouseDown.*target.*current.*(?:close|handle[A-Z])" -A 2

# Search for any documentation or comments about modal/fullscreen interaction
rg -i "fullscreen.*interaction|modal.*click|background.*click" --type md

Length of output: 562


Script:

#!/bin/bash
# Search for modal-related components and their click handlers
rg -U "Modal.*|modal.*" --type tsx -A 5

# Search for other fullscreen implementations
ast-grep --pattern 'className={$_("fixed inset-0"$$$)}'

Length of output: 127


Script:

#!/bin/bash
# Search for modal-related components and their click handlers
rg -U "Modal.*|modal.*" --type ts --type jsx --type tsx -A 5

# Look for any image-related documentation
fd -e md -x grep -l -i "image.*interaction|fullscreen.*image" {} \;

Length of output: 163


246-249: Security check for opening in new tab
While generally safe, opening external links can pose potential security or user experience concerns. If the image source can come from user input, confirm that the domain is trusted or sanitized.

✅ Verification successful

Let me investigate the source of the image to determine if it can come from user input and if there are any security measures in place.


Let me check the implementation of the custom image component to understand how the image source is handled.


Let me check one more aspect to understand how the image source is processed before being set.


Based on the code analysis, I can now generate the final response:

Image source is properly handled through controlled upload process
The image source (src) used in the full-screen view comes from a controlled upload process. The code shows that images are processed through CustomImageUploader, validated during upload, and resolved through a getImageSource function that processes the path. The source cannot be directly manipulated by users as it's handled through the editor's controlled workflow.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# First, let's find where the 'src' prop is defined and how it's handled
ast-grep --pattern 'const src = $_'

# Let's also look for any image upload or URL handling related to this component
rg -l "src.*=" packages/editor/src/core/extensions/custom-image/

# Check for any sanitization or validation of image URLs
rg -l "sanitize|validate.*url|validateUrl" packages/editor/src/core/extensions/custom-image/

Length of output: 972


Script:

#!/bin/bash
# Let's examine the custom image implementation and how src is handled
rg -A 5 "src.*=" packages/editor/src/core/extensions/custom-image/custom-image.ts

# Check the image node component implementation
cat packages/editor/src/core/extensions/custom-image/components/image-node.tsx

# Look for image upload handling
rg -A 5 "upload" packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx

Length of output: 6997


Script:

#!/bin/bash
# Check the image block component to see how src is handled before being displayed
cat packages/editor/src/core/extensions/custom-image/components/image-block.tsx

# Look for any URL processing or source handling in the custom image implementation
rg -A 5 "getImageSource" packages/editor/src/core/extensions/custom-image/custom-image.ts

Length of output: 12343


36-58: Consider verifying image load completion before calculations
Here, the image dimensions are derived from raw numbers and used to compute magnification and set positions. If the image hasn’t fully loaded, these calculations might be premature for certain browsers or slow networks. Consider verifying the onLoad event before setting initial magnification/positions.

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: 2

🧹 Nitpick comments (3)
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (3)

17-20: Consider adjusting MIN_ZOOM for better support of large images

The current MIN_ZOOM value of 0.5 might be too restrictive when viewing very large images. Consider lowering it to 0.1 to allow users to zoom out further when needed.

-const MIN_ZOOM = 0.5;
+const MIN_ZOOM = 0.1;
-const ZOOM_STEPS = [0.5, 1, 1.5, 2];
+const ZOOM_STEPS = [0.1, 0.25, 0.5, 1, 1.5, 2];

36-58: Add window resize handler for responsive behavior

The initial magnification calculation considers viewport dimensions, but these dimensions could change if the user resizes their window. Consider adding a window resize handler to recalculate the optimal magnification.

 useEffect(() => {
   if (!isFullScreenEnabled) return;
+  const handleResize = () => {
+    if (!imgRef.current) return;
+    const viewportWidth = window.innerWidth * 0.9;
+    const viewportHeight = window.innerHeight * 0.75;
+    const imageWidth = widthInNumber;
+    const imageHeight = imageWidth / aspectRatio;
+    
+    const widthRatio = viewportWidth / imageWidth;
+    const heightRatio = viewportHeight / imageHeight;
+    
+    setInitialMagnification(Math.min(widthRatio, heightRatio));
+  };
+
+  window.addEventListener('resize', handleResize);
+  return () => window.removeEventListener('resize', handleResize);
 }, [isFullScreenEnabled, widthInNumber, aspectRatio]);

210-223: Add double-click to reset zoom

Consider adding a double-click handler to reset the zoom level and position to initial state, which is a common feature in image viewers.

 <img
   ref={setImageRef}
   src={src}
   className="read-only-image rounded-lg"
+  onDoubleClick={() => {
+    setMagnification(1);
+    if (imgRef.current) {
+      imgRef.current.style.left = "0px";
+      imgRef.current.style.top = "0px";
+    }
+  }}
   style={{
     width: `${widthInNumber * initialMagnification}px`,
     maxWidth: "none",
     maxHeight: "none",
     aspectRatio,
     position: "relative",
     transform: `scale(${magnification})`,
     transformOrigin: "center",
     transition: "width 0.2s ease, transform 0.2s ease",
   }}
   onMouseDown={handleMouseDown}
 />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af0cf97 and 6218cc3.

📒 Files selected for processing (1)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (3 hunks)
🔇 Additional comments (1)
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (1)

171-186: LGTM! Event listeners are properly managed

The event listeners are correctly set up with proper cleanup in the useEffect hook. The use of passive: false for the wheel event is appropriate for preventing default scroll behavior.

Comment on lines +144 to 169
const handleWheel = useCallback(
(e: WheelEvent) => {
if (!imgRef.current || !isFullScreenEnabled) return;

e.preventDefault();

// Handle pinch-to-zoom
if (e.ctrlKey) {
const delta = e.deltaY;
setMagnification((prev) => {
const newZoom = prev * (1 - delta * ZOOM_SPEED);
const clampedZoom = Math.min(Math.max(newZoom, MIN_ZOOM), MAX_ZOOM);

// Reset position when zoom matches initial magnification
if (clampedZoom === 1 && imgRef.current) {
imgRef.current.style.left = "0px";
imgRef.current.style.top = "0px";
}

return clampedZoom;
});
return;
}
},
[handleClose]
[isFullScreenEnabled, magnification]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix handleWheel implementation issues

Two issues need attention:

  1. The wheel zoom sensitivity might be too high for precise control
  2. The dependency array is missing dependencies used in the callback
-const ZOOM_SPEED = 0.05;
+const ZOOM_SPEED = 0.01;

 const handleWheel = useCallback(
   (e: WheelEvent) => {
     // ... existing code ...
   },
-  [isFullScreenEnabled, magnification]
+  [isFullScreenEnabled]
 );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +225 to +244
<div className="flex items-center">
<button
type="button"
onClick={() => handleMagnification("decrease")}
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification <= MIN_ZOOM}
>
<Minus className="size-4" />
</button>
<span className="text-sm w-12 text-center text-white">{Math.round(100 * magnification)}%</span>
<button
type="button"
onClick={() => handleMagnification("increase")}
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification >= MAX_ZOOM}
>
<Plus className="size-4" />
</button>
</div>
<button
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance accessibility for zoom controls

The zoom controls need proper ARIA labels and roles for better accessibility.

 <button
   type="button"
   onClick={() => handleMagnification("decrease")}
+  aria-label="Zoom out"
   className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
   disabled={magnification <= MIN_ZOOM}
 >
   <Minus className="size-4" />
 </button>
-<span className="text-sm w-12 text-center text-white">
+<span className="text-sm w-12 text-center text-white" role="status" aria-live="polite">
   {Math.round(100 * magnification)}%
 </span>
 <button
   type="button"
   onClick={() => handleMagnification("increase")}
+  aria-label="Zoom in"
   className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
   disabled={magnification >= MAX_ZOOM}
 >
   <Plus className="size-4" />
 </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="fixed bottom-10 left-1/2 -translate-x-1/2 flex items-center justify-center gap-1 rounded-md border border-white/20 py-2 divide-x divide-white/20 bg-black">
<div className="flex items-center">
<button
type="button"
onClick={() => handleMagnification("decrease")}
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification <= MIN_ZOOM}
>
<Minus className="size-4" />
</button>
<span className="text-sm w-12 text-center text-white">{Math.round(100 * magnification)}%</span>
<button
type="button"
onClick={() => handleMagnification("increase")}
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification >= MAX_ZOOM}
>
<Plus className="size-4" />
</button>
</div>
<div className="fixed bottom-10 left-1/2 -translate-x-1/2 flex items-center justify-center gap-1 rounded-md border border-white/20 py-2 divide-x divide-white/20 bg-black">
<div className="flex items-center">
<button
type="button"
onClick={() => handleMagnification("decrease")}
aria-label="Zoom out"
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification <= MIN_ZOOM}
>
<Minus className="size-4" />
</button>
<span className="text-sm w-12 text-center text-white" role="status" aria-live="polite">{Math.round(100 * magnification)}%</span>
<button
type="button"
onClick={() => handleMagnification("increase")}
aria-label="Zoom in"
className="size-6 grid place-items-center text-white/60 hover:text-white disabled:text-white/30 transition-colors duration-200"
disabled={magnification >= MAX_ZOOM}
>
<Plus className="size-4" />
</button>
</div>

@Palanikannan1437 Palanikannan1437 changed the title fix: zooming for fullscreen images [PE-102] fix: zooming for fullscreen images Dec 23, 2024
@pushya22 pushya22 merged commit ac47cc6 into preview Dec 23, 2024
11 checks passed
@pushya22 pushya22 deleted the fix/image-fullscreen branch December 23, 2024 14:33
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.

3 participants