FE-528: Fit Petrinaut zoom out to size of net#8611
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
PR SummaryMedium Risk Overview This introduces Reviewed by Cursor Bugbot for commit 4e6c960. Bugbot is set up for automated code reviews on this repo. Configure here. |
🤖 Augment PR SummarySummary: Improves Petrinaut zoom-out behavior by dynamically constraining the minimum zoom based on the current net bounds and viewport size. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Min zoom ratchets up, permanently preventing zoom-out
- The min-zoom calculation now derives from previous
minZoomstate (with a one-time initialization cap) so zooming in no longer ratchets the floor upward and users can zoom back out.
- The min-zoom calculation now derives from previous
Or push these changes by commenting:
@cursor push dd118ea3c1
Preview (dd118ea3c1)
diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
--- a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
+++ b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
@@ -51,7 +51,7 @@
(
instance: PetrinautReactFlowInstance | null,
canvasContainer: React.RefObject<HTMLDivElement | null>,
- setMinZoom: (minZoom: number) => void,
+ setMinZoom: React.Dispatch<React.SetStateAction<number>>,
) => {
const minMaxCoords = instance?.getNodesBounds(instance.getNodes());
const viewportSize = canvasContainer.current?.getBoundingClientRect();
@@ -62,12 +62,17 @@
viewportSize.width / minMaxCoords.width,
);
const currentZoom = instance?.getViewport().zoom;
- // Don't reduce the zoom level below the users current zoom
- const safeZoom = currentZoom
- ? Math.min(currentZoom, newZoom * ZOOM_PADDING)
- : newZoom * ZOOM_PADDING;
+ setMinZoom((currentMinZoom) => {
+ // Don't increase min zoom beyond the currently configured minimum.
+ // On first initialization (min zoom still 0), cap to the user's current zoom.
+ if (currentMinZoom === 0) {
+ return currentZoom
+ ? Math.min(currentZoom, newZoom * ZOOM_PADDING)
+ : newZoom * ZOOM_PADDING;
+ }
- setMinZoom(safeZoom);
+ return Math.min(currentMinZoom, newZoom * ZOOM_PADDING);
+ });
}
},
100,This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
|
Ideally we want to define everything as a graph of derived states, and not rely on ReactFlow for calculations: const RESIZE_DEBOUNCE = 200;
const canvasDimensions = useDebounceValue(useResizeObserver(canvasContainer.current), RESIZE_DEBOUNCE)
const petrinetDimensions = computeNetDimensions(sdcpn)
const minZoom = computeMinZoom(sdcpn, canvasDimensions) |
There are actually 3 seperate values we are using to define the new minzoom value: the size of the viewport, the size of the net, and the users current zoom value. The biggest performance hits are during the resize and zoom events, where these values change continuously and if not debounced will trigger large amounts of re-renders. That means that if we split the state into its corresponding parts: a) the zoom and viewport size values will not be perfectly accurate if used by other functions as they will be debounced. This could be confusing. And b) because we are debouncing both values seperately, we will trigger double the rerenders, so its going to be less performant |
That said, it may be still be better to do it your way vs adding a second observable if we need to reuse the viewport values for some other function so not totally against it. |
The only debounced value here would be the canvas dimensions (trigger by resizing). Change in user zoom should not trigger re-calculation of const RESIZE_DEBOUNCE = 200;
const canvasDimensions = useDebounceValue(useResizeObserver(canvasContainer.current), RESIZE_DEBOUNCE)
const petrinetDimensions = computeNetDimensions(sdcpn)
const minZoom = computeMinZoom(sdcpn, canvasDimensions)
const actualMinZoom = Math.min(userZoom, minZoom) |
libs/@hashintel/petrinaut/src/views/SDCPN/hooks/util/useDebounceCallback.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb7f3a7. Configure here.
2748048 to
4e6c960
Compare


🌟 What is the purpose of this PR?
This PR introduces improves the way that zoom is handled in petrinaut. Rather than a fixed value, which might be too small to view large nets, or an infinite zoom out, which make it easy to lose your net if you zoom out too far, I'm introducing a dynamic zoom that maxes out at a level just large enough to view the entire net, but no larger.
There's a bit of extra logic to handle debouncing, screen resizing, and a seamless UX so that users don't notice when we change the maximum zoom underneath them.
Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
🕸️ Does this require a change to the Turbo Graph?
❓ How to test this?