Skip to content

Fix image viewer: broken pan, missing wheel zoom, download navigates instead of saving#926

Merged
PythonSmall-Q merged 4 commits intofeat/resize-picturefrom
copilot/sub-pr-924
Mar 8, 2026
Merged

Fix image viewer: broken pan, missing wheel zoom, download navigates instead of saving#926
PythonSmall-Q merged 4 commits intofeat/resize-picturefrom
copilot/sub-pr-924

Conversation

Copy link
Contributor

Copilot AI commented Mar 8, 2026

Three bugs in the ImageEnlarger image viewer modal: dragging only worked within the pre-transform layout bounds of the image element, mouse wheel had no effect, and "保存图片" navigated to the image URL instead of triggering a download.

Changes

  • Drag/pan area — moved mousedown from ModalImage to ModalContent. transform: scale() doesn't resize the element's hit area, so dragging outside the original layout bounds was silently dropped. Attaching to the full-screen container fixes this. Guard added to skip drag on BUTTON targets (nav arrows). Cursor style now synced on both ModalImage and ModalContent.

  • Mouse wheel zoom — added wheel listener on ModalContent (passive: false to suppress scroll). Scroll up = zoom in, scroll down = zoom out, clamped to [MinZoom, MaxZoom].

  • Download — replaced fetchsaveAs (FileSaver) with GM_xmlhttpRequestURL.createObjectURL<a download> click. GM_xmlhttpRequest bypasses CORS; download on a blob: URL is always same-origin so the save dialog reliably triggers. Blob URL revoked after 100 ms. Error fallback adds target="_blank" to avoid same-tab navigation.

GM_xmlhttpRequest({
    method: "GET", url: src, responseType: "blob",
    onload: (resp) => {
        let BlobUrl = URL.createObjectURL(resp.response);
        let Link = document.createElement("a");
        Link.href = BlobUrl; Link.download = filename;
        document.body.appendChild(Link); Link.click(); document.body.removeChild(Link);
        setTimeout(() => URL.revokeObjectURL(BlobUrl), 100);
    },
    onerror: () => { /* open in new tab as last resort */ }
});

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.


Summary by cubic

Add a modal image viewer with on-screen prev/next controls, arrow-key and swipe navigation, drag-to-pan when zoomed, mouse wheel zoom, and a download that saves the file without leaving the page.

  • New Features
    • Modal viewer for enlarged images with prev/next buttons and swipe/arrow-key navigation (hidden if one image, disabled at edges).
    • Zoom and pan: mouse wheel zoom; drag-to-pan on mouse and touch; pan resets on zoom reset or image change.
    • Reliable download: fetch via GM_xmlhttpRequest, save via blob URL with fallback to a new tab (no redirect).

Written for commit 6f310f3. Summary will update on new commits.

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
@pull-request-size pull-request-size bot added size/L and removed size/XS labels Mar 8, 2026
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Copilot AI changed the title [WIP] Add image enlarger feature with modal viewer ImageEnlarger: add drag-to-pan, swipe navigation, and direct download Mar 8, 2026
Copilot stopped work on behalf of PythonSmall-Q due to an error March 8, 2026 14:33
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Copilot AI changed the title ImageEnlarger: add drag-to-pan, swipe navigation, and direct download Fix image viewer: broken pan, missing wheel zoom, download navigates instead of saving Mar 8, 2026
@PythonSmall-Q PythonSmall-Q marked this pull request as ready for review March 8, 2026 14:49
Copilot AI review requested due to automatic review settings March 8, 2026 14:49
@PythonSmall-Q PythonSmall-Q merged commit 8ced3d0 into feat/resize-picture Mar 8, 2026
2 checks passed
@PythonSmall-Q PythonSmall-Q deleted the copilot/sub-pr-924 branch March 8, 2026 14:49
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes usability bugs in the ImageEnlarger modal viewer by improving interaction hit areas, restoring wheel zoom, adding image-to-image navigation, and making “保存图片” reliably trigger a download.

Changes:

  • Move drag/pan handling to the modal container and add mouse/touch pan + swipe navigation.
  • Add wheel zoom (clamped) and prev/next buttons + arrow-key navigation.
  • Replace direct-link download with GM_xmlhttpRequest blob download + fallback to opening in a new tab.
Comments suppressed due to low confidence (1)

XMOJ.user.js:5968

  • PanX/PanY persist when zooming back down to CurrentZoom === 1 via the wheel/zoom-out button, leaving the image offset while panning is disabled (since drag is gated on CurrentZoom > 1). Consider resetting PanX/PanY to 0 whenever zoom is clamped back to 1 (or below) so the image recenters when exiting zoom mode.
                ModalContent.addEventListener("wheel", (e) => {
                    e.preventDefault();
                    let ZoomDelta = e.deltaY > 0 ? -ZoomStep : ZoomStep;
                    CurrentZoom = Math.max(MinZoom, Math.min(MaxZoom, CurrentZoom + ZoomDelta));
                    UpdateImageSize();
                }, { passive: false });
                
                // Navigation button clicks
                PrevBtn.addEventListener("click", (e) => {
                    e.stopPropagation();
                    NavigateTo(CurrentImageIndex - 1);
                });
                
                NextBtn.addEventListener("click", (e) => {
                    e.stopPropagation();
                    NavigateTo(CurrentImageIndex + 1);
                });
                
                // Zoom controls
                ZoomInBtn.addEventListener("click", () => {
                    CurrentZoom = Math.min(CurrentZoom + ZoomStep, MaxZoom);
                    UpdateImageSize();
                });
                
                ZoomOutBtn.addEventListener("click", () => {
                    CurrentZoom = Math.max(CurrentZoom - ZoomStep, MinZoom);
                    UpdateImageSize();

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

let UpdateImageSize = () => {
ModalImage.style.transform = `scale(${CurrentZoom})`;
ModalImage.style.transition = "transform 0.2s ease";
ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`;
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

transform = translate(...) scale(...) scales the translation as zoom changes, so panning speed becomes multiplied by CurrentZoom (e.g., a 10px drag moves ~20px at 2x). To keep pan in screen pixels, apply transforms in the opposite order (scale then translate) or divide the translation by the zoom factor.

Suggested change
ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`;
ModalImage.style.transform = `scale(${CurrentZoom}) translate(${PanX}px, ${PanY}px)`;

Copilot uses AI. Check for mistakes.
Comment on lines +5988 to +5995
let BlobUrl = URL.createObjectURL(resp.response);
let Link = document.createElement("a");
Link.href = BlobUrl;
Link.download = filename;
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
setTimeout(() => URL.revokeObjectURL(BlobUrl), 100);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

GM_xmlhttpRequest calls onload even for non-2xx HTTP responses. The current implementation will still create a blob and prompt a download for 404/500 HTML error pages. Please check resp.status (and that resp.response is present) before downloading, and fall back to the onerror/new-tab behavior when the request fails.

Suggested change
let BlobUrl = URL.createObjectURL(resp.response);
let Link = document.createElement("a");
Link.href = BlobUrl;
Link.download = filename;
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
setTimeout(() => URL.revokeObjectURL(BlobUrl), 100);
if (resp && resp.status >= 200 && resp.status < 300 && resp.response) {
let BlobUrl = URL.createObjectURL(resp.response);
let Link = document.createElement("a");
Link.href = BlobUrl;
Link.download = filename;
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
setTimeout(() => URL.revokeObjectURL(BlobUrl), 100);
} else {
let Link = document.createElement("a");
Link.href = src;
Link.download = filename;
Link.target = "_blank";
document.body.appendChild(Link);
Link.click();
document.body.removeChild(Link);
}

Copilot uses AI. Check for mistakes.
Comment on lines +5912 to +5917
ModalContent.addEventListener("mousedown", (e) => {
if (CurrentZoom <= 1) return;
if (e.target.tagName.toUpperCase() === "BUTTON") return;
IsDragging = true;
DragStartX = e.clientX;
DragStartY = e.clientY;
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

The drag handler starts panning on any mouse button; right/middle click can unintentionally set IsDragging = true and suppress expected behavior. Gate drag start to the primary button only (e.g., e.button === 0).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants