-
Notifications
You must be signed in to change notification settings - Fork 1
feature: minimap drag event correction and code refactors #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ Deploy Preview for virtual-react-json-diff ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @utkuakyuz, I'm Gemini Code Assist1 ! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces significant architectural improvements to the minimap component by refactoring its core functionalities into two new custom React hooks: useDragScroll
and useMinimapDraw
. This change addresses an existing issue related to minimap drag event behavior and drastically cleans up the DiffMinimap
component by delegating responsibilities, making the codebase more modular and maintainable.
Highlights
- Minimap Drag Event Correction: The core logic for handling minimap drag events and scrolling has been extracted into a dedicated
useDragScroll
custom hook. This hook manages mouse interactions (mousedown, mousemove, mouseup) to enable smooth and accurate navigation within the minimap, including corrections for boundary conditions. - Minimap Drawing Logic Extraction: All minimap drawing responsibilities, including rendering diff lines, search highlights, and the scrollbox, have been encapsulated within a new
useMinimapDraw
custom hook. This separation improves modularity and makes the rendering logic more manageable. - Major Code Refactoring and Splitting: The
DiffMinimap.tsx
component has undergone significant refactoring. It now primarily orchestrates theuseDragScroll
anduseMinimapDraw
hooks, leading to a cleaner, more readable, and less coupled component structure. This adheres to the principle of separation of concerns.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request does a great job of refactoring the DiffMinimap
component by extracting complex logic into custom hooks (useDragScroll
and useMinimapDraw
). This significantly improves code organization and separation of concerns. My review focuses on a few issues related to React hooks usage, particularly state management for dragging and dependency arrays, which can lead to bugs. Addressing these will make the new implementation more robust and maintainable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isDragging
flag is used to determine the visual state of the minimap's scrollbar. Using useRef
for this is incorrect because ref updates do not trigger re-renders. This causes a bug where the scrollbar remains highlighted after a drag operation is completed, as the component does not re-render to reflect the change in isDragging
state.
To fix this, isDragging
should be managed with useState
. This will ensure the component re-renders when the dragging state changes. You will need to update the hook's return type and pass the isDragging
state (not a ref) to useMinimapDraw
. This change also makes the useEffect
on lines 111-118 redundant and it should be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This useEffect
adds a global mouseup
listener to set isDragging.current
to false. This is redundant with the logic in handleWindowMouseUp
, which already handles cleaning up the dragging state and its associated event listeners.
More importantly, as mentioned in another comment, isDragging
should be a state variable. Once that is addressed, this useEffect
becomes entirely unnecessary and should be removed to avoid confusion and redundant event listeners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency array for drawMinimap
is incorrect. It should depend on drawScrollBox
which it calls, and isDragging
which determines the color. The current flattened dependency list is incomplete and hard to maintain.
Assuming isDragging
is converted to a state variable as per my other comment, the dependency array should be updated to include drawScrollBox
and isDragging
. This will ensure drawMinimap
is re-created and the minimap is redrawn when the dragging state or other drawing-related data changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency array for handleMouseMove
is missing ROW_HEIGHT
, which is used on line 78. Although ROW_HEIGHT
is currently stable because it's memoized with an empty dependency array, it's important to follow the rules of hooks and include all dependencies. This prevents subtle bugs if ROW_HEIGHT
's calculation ever changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor correction: The filename in the comment is useMinimapDraw.js
, but the file extension is .ts
.
/gemini review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request is a great step towards improving code organization by refactoring the minimap logic into custom hooks. The separation of concerns into useDragScroll
and useMinimapDraw
is a solid architectural choice. However, I've identified some critical issues in the implementation of these hooks, particularly concerning event listener management in useDragScroll
, which could lead to memory leaks and bugs. Additionally, there are significant performance problems in the drawing logic within useMinimapDraw
that could degrade user experience. My review includes detailed suggestions to address these issues by adopting more idiomatic React patterns and optimizing the drawing pipeline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation for handling drag events has critical flaws related to event listener management and state closures in React. Adding event listeners inside a useCallback
like handleMouseDown
and removing them in another can lead to memory leaks if the function references change between adding and removing. Furthermore, handleWindowMouseMove
has a stale closure over the isDragging
state because it's not included in its dependency array, which will prevent dragging from working at all.
A much safer and more idiomatic React approach is to use a useEffect
hook to manage side effects like window event listeners. The effect should be dependent on the isDragging
state.
import { useCallback, useState, useEffect } from "react"; type UseDragScrollParams = { height: number; totalLines: number; viewportHeight: number; ROW_HEIGHT: number; onScroll: (scrollTop: number) => void; containerRef: React.RefObject<HTMLDivElement>; }; type UseDragScrollReturn = { handleMouseDown: (e: React.MouseEvent) => void; isDragging: boolean; }; export function useDragScroll({ height, totalLines, viewportHeight, ROW_HEIGHT, onScroll, containerRef, }: UseDragScrollParams): UseDragScrollReturn { const [isDragging, setIsDragging] = useState(false); const handleBoundaryScroll = useCallback((e: MouseEvent | React.MouseEvent<HTMLDivElement>["nativeEvent"], rect: DOMRect): void => { const relativeY = e.clientY - rect.top; const maxScroll = Math.max(0, (totalLines * ROW_HEIGHT) - height); const cursorBottom = e.clientY + viewportHeight / 2; let scrollTop; if (cursorBottom > rect.bottom) { const constrainedRelativeY = rect.bottom - rect.top - viewportHeight / 2; scrollTop = ((constrainedRelativeY - viewportHeight / 2) / height) * totalLines * ROW_HEIGHT; } else { scrollTop = ((relativeY - viewportHeight / 2) / height) * totalLines * ROW_HEIGHT; } if (!Number.isNaN(scrollTop) && Number.isFinite(scrollTop)) { onScroll(Math.min(maxScroll, Math.max(0, scrollTop))); } }, [height, totalLines, viewportHeight, ROW_HEIGHT, onScroll]); useEffect(() => { const handleWindowMouseMove = (e: MouseEvent) => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); handleBoundaryScroll(e, rect); }; const handleWindowMouseUp = () => { setIsDragging(false); }; if (isDragging) { window.addEventListener("mousemove", handleWindowMouseMove); window.addEventListener("mouseup", handleWindowMouseUp); } return () => { window.removeEventListener("mousemove", handleWindowMouseMove); window.removeEventListener("mouseup", handleWindowMouseUp); }; }, [isDragging, containerRef, handleBoundaryScroll]); const handleMouseDown = useCallback((e: React.MouseEvent) => { if (!containerRef.current) return; setIsDragging(true); const rect = containerRef.current.getBoundingClientRect(); handleBoundaryScroll(e.nativeEvent, rect); }, [containerRef, handleBoundaryScroll]); return { handleMouseDown, isDragging, }; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current drawing logic is inefficient. drawScrollBox
calls drawDifferencesInMinimap
, which iterates over all diff lines. In DiffMinimap.tsx
, handleMouseMove
calls drawMinimap
(which calls drawScrollBox
) and then potentially drawScrollBox
again. This results in redrawing all the diff lines multiple times on every mouse movement, causing performance issues.
The drawing of diffs should be separated from drawing the scrollbar. drawMinimap
should be responsible for drawing the diffs once, and then drawing the scrollbar. drawScrollBox
should only draw the scrollbar rectangle. This will also simplify the dependency array of drawMinimap
.
// Draw the scroll box const drawScrollBox = useCallback((ctx: CanvasRenderingContext2D, color: string) => { const totalContentHeight = totalLines * ROW_HEIGHT; const viewportTop = (currentScrollTop / totalContentHeight) * height; ctx.fillStyle = color; ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); }, [totalLines, ROW_HEIGHT, currentScrollTop, height, miniMapWidth, viewportHeight]); const drawMinimap = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); drawDifferencesInMinimap(ctx); if (!isDragging) { drawScrollBox(ctx, MINIMAP_SCROLL_COLOR); } else { drawScrollBox(ctx, MINIMAP_HOVER_SCROLL_COLOR); } }, [canvasRef, drawDifferencesInMinimap, isDragging, drawScrollBox]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This useEffect
calls drawMinimap
whenever drawMinimap
changes. However, the DiffMinimap
component, which uses this hook, already has an identical useEffect
to call drawMinimap
. This results in drawMinimap
being called twice for every change, which is redundant and inefficient. The responsibility of when to draw should lie with the component using the hook. This useEffect
should be removed.
The reviews of Gemini needs some rework because directly implementing it brokes the logic.
Right now there is no memory leak nor visual issue. So for quick fix this pull request is merged and closed.
Further work will include code refactors.
Closes #6
This PR includes several commits including solution for issue #6 and also major code refactors and code splits on diffMiniMap