mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-12 00:04:02 +02:00
Avoid to scroll too much when the thumbnail is at the bottom of the sidebar (bug 2016693)
This commit is contained in:
parent
f626a368a5
commit
4b7fa1c003
@ -126,6 +126,12 @@ class PDFThumbnailViewer {
|
||||
|
||||
#isCut = false;
|
||||
|
||||
#isOneColumnView = false;
|
||||
|
||||
#scrollableContainerWidth = 0;
|
||||
|
||||
#scrollableContainerHeight = 0;
|
||||
|
||||
/**
|
||||
* @param {PDFThumbnailViewerOptions} options
|
||||
*/
|
||||
@ -716,10 +722,28 @@ class PDFThumbnailViewer {
|
||||
}
|
||||
|
||||
#moveDraggedContainer(dx, dy) {
|
||||
this.#draggedImageOffsetX += dx;
|
||||
this.#draggedImageOffsetY += dy;
|
||||
if (this.#isOneColumnView) {
|
||||
dx = 0;
|
||||
}
|
||||
if (
|
||||
this.#draggedImageX + dx < 0 ||
|
||||
this.#draggedImageX + this.#draggedImageWidth + dx >
|
||||
this.#scrollableContainerWidth
|
||||
) {
|
||||
dx = 0;
|
||||
}
|
||||
if (
|
||||
this.#draggedImageY + dy < 0 ||
|
||||
this.#draggedImageY + this.#draggedImageHeight + dy >
|
||||
this.#scrollableContainerHeight
|
||||
) {
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
this.#draggedImageX += dx;
|
||||
this.#draggedImageY += dy;
|
||||
this.#draggedImageOffsetX += dx;
|
||||
this.#draggedImageOffsetY += dy;
|
||||
this.#draggedContainer.style.translate = `${this.#draggedImageOffsetX}px ${this.#draggedImageOffsetY}px`;
|
||||
if (
|
||||
this.#draggedImageY + this.#draggedImageHeight >
|
||||
@ -727,7 +751,7 @@ class PDFThumbnailViewer {
|
||||
) {
|
||||
this.scrollableContainer.scrollTop = Math.min(
|
||||
this.scrollableContainer.scrollTop + PIXELS_TO_SCROLL_WHEN_DRAGGING,
|
||||
this.scrollableContainer.scrollHeight
|
||||
this.#scrollableContainerHeight
|
||||
);
|
||||
} else if (this.#draggedImageY < this.#currentScrollTop) {
|
||||
this.scrollableContainer.scrollTop = Math.max(
|
||||
@ -839,6 +863,11 @@ class PDFThumbnailViewer {
|
||||
lastSpace: (positionsLastX.at(-1) - lastRightX) / 2,
|
||||
bbox,
|
||||
};
|
||||
this.#isOneColumnView = positionsX.length === 1;
|
||||
({
|
||||
clientWidth: this.#scrollableContainerWidth,
|
||||
scrollHeight: this.#scrollableContainerHeight,
|
||||
} = this.scrollableContainer);
|
||||
}
|
||||
|
||||
#addEventListeners() {
|
||||
@ -950,6 +979,7 @@ class PDFThumbnailViewer {
|
||||
pointerId: dragPointerId,
|
||||
} = e;
|
||||
if (
|
||||
e.button !== 0 || // Skip right click.
|
||||
this.#pagesMapper.copiedPageNumbers?.length > 0 ||
|
||||
!isNaN(this.#lastDraggedOverIndex) ||
|
||||
!draggedImage.classList.contains("thumbnailImageContainer")
|
||||
@ -969,11 +999,18 @@ class PDFThumbnailViewer {
|
||||
// same position on the thumbnail, we need to adjust the offset
|
||||
// accordingly.
|
||||
const scaleFactor = PDFThumbnailViewer.#getScaleFactor(draggedImage);
|
||||
this.#draggedImageOffsetX =
|
||||
((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) / scaleFactor;
|
||||
this.#draggedImageOffsetY =
|
||||
((scaleFactor - 1) * e.layerY + draggedImage.offsetTop) / scaleFactor;
|
||||
|
||||
if (this.#isOneColumnView) {
|
||||
this.#draggedImageOffsetX =
|
||||
draggedImage.offsetLeft +
|
||||
((scaleFactor - 1) * 0.5 * draggedImage.offsetWidth) / scaleFactor;
|
||||
} else {
|
||||
this.#draggedImageOffsetX =
|
||||
((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) /
|
||||
scaleFactor;
|
||||
}
|
||||
this.#draggedImageX = thumbnail.offsetLeft + this.#draggedImageOffsetX;
|
||||
this.#draggedImageY = thumbnail.offsetTop + this.#draggedImageOffsetY;
|
||||
this.#draggedImageWidth = draggedImage.offsetWidth / scaleFactor;
|
||||
@ -983,16 +1020,16 @@ class PDFThumbnailViewer {
|
||||
"pointermove",
|
||||
ev => {
|
||||
const { clientX: x, clientY: y, pointerId } = ev;
|
||||
if (
|
||||
pointerId !== dragPointerId ||
|
||||
(Math.abs(x - clickX) <= DRAG_THRESHOLD_IN_PIXELS &&
|
||||
Math.abs(y - clickY) <= DRAG_THRESHOLD_IN_PIXELS)
|
||||
) {
|
||||
// Not enough movement to be considered a drag.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(this.#lastDraggedOverIndex)) {
|
||||
if (
|
||||
pointerId !== dragPointerId ||
|
||||
(Math.abs(x - clickX) <= DRAG_THRESHOLD_IN_PIXELS &&
|
||||
Math.abs(y - clickY) <= DRAG_THRESHOLD_IN_PIXELS)
|
||||
) {
|
||||
// Not enough movement to be considered a drag.
|
||||
return;
|
||||
}
|
||||
|
||||
// First movement while dragging.
|
||||
this.#onStartDragging(thumbnail);
|
||||
const stopDragging = (_e, isDropping = false) => {
|
||||
@ -1043,6 +1080,18 @@ class PDFThumbnailViewer {
|
||||
passive: false,
|
||||
signal,
|
||||
});
|
||||
window.addEventListener(
|
||||
"keydown",
|
||||
kEv => {
|
||||
if (
|
||||
kEv.key === "Escape" &&
|
||||
!isNaN(this.#lastDraggedOverIndex)
|
||||
) {
|
||||
stopDragging(kEv);
|
||||
}
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
}
|
||||
|
||||
const dx = x - prevDragX;
|
||||
@ -1151,6 +1200,16 @@ class PDFThumbnailViewer {
|
||||
}
|
||||
}
|
||||
|
||||
// Given the drag center (x, y), find the drop slot index: the drag marker
|
||||
// will be placed after thumbnail[index], or before all thumbnails if index
|
||||
// is -1. Returns null when the drop slot hasn't changed (no marker update
|
||||
// needed), or [index, space] where space is the gap (in px) between
|
||||
// thumbnails at that slot, used to position the marker.
|
||||
//
|
||||
// positionsX holds the x-center of each column, positionsY the y-center of
|
||||
// each row. positionsLastX holds the x-centers for an incomplete last row
|
||||
// (when the total number of thumbnails is not a multiple of the column
|
||||
// count).
|
||||
#findClosestThumbnail(x, y) {
|
||||
if (!this.#thumbnailsPositions) {
|
||||
this.#computeThumbnailsPosition();
|
||||
@ -1163,6 +1222,9 @@ class PDFThumbnailViewer {
|
||||
lastSpace: lastSpaceBetweenThumbnails,
|
||||
} = this.#thumbnailsPositions;
|
||||
const lastDraggedOverIndex = this.#lastDraggedOverIndex;
|
||||
|
||||
// Fast-path: reconstruct the row/col of the previous drop slot and check
|
||||
// whether (x, y) still falls inside the same cell's bounds.
|
||||
let xPos = lastDraggedOverIndex % positionsX.length;
|
||||
let yPos = Math.floor(lastDraggedOverIndex / positionsX.length);
|
||||
let xArray = yPos === positionsY.length - 1 ? positionsLastX : positionsX;
|
||||
@ -1176,28 +1238,58 @@ class PDFThumbnailViewer {
|
||||
return null;
|
||||
}
|
||||
|
||||
yPos = binarySearchFirstItem(positionsY, cy => y < cy) - 1;
|
||||
xArray =
|
||||
yPos === positionsY.length - 1 && positionsLastX.length > 0
|
||||
? positionsLastX
|
||||
: positionsX;
|
||||
xPos = Math.max(0, binarySearchFirstItem(xArray, cx => x < cx) - 1);
|
||||
if (yPos < 0) {
|
||||
if (xPos <= 0) {
|
||||
xPos = -1;
|
||||
let index;
|
||||
// binarySearchFirstItem returns the first row index whose center is below
|
||||
// y, i.e. the first i such that positionsY[i] > y.
|
||||
yPos = binarySearchFirstItem(positionsY, cy => y < cy);
|
||||
if (this.#isOneColumnView) {
|
||||
// In a single column the drop slot is simply the row boundary: the marker
|
||||
// goes after row (yPos - 1), meaning before row yPos. index = -1 when y
|
||||
// is above the first thumbnail's center (drop before thumbnail 0).
|
||||
index = yPos - 1;
|
||||
} else {
|
||||
// Grid layout: first pick the nearest row, then the nearest column.
|
||||
|
||||
if (yPos === positionsY.length) {
|
||||
// y is below the last row's center — clamp to the last row.
|
||||
yPos = positionsY.length - 1;
|
||||
} else {
|
||||
// Choose between the row just above (yPos - 1) and the row at yPos by
|
||||
// comparing distances, so the marker snaps to whichever row center is
|
||||
// closer to y.
|
||||
const dist1 = Math.abs(positionsY[yPos - 1] - y);
|
||||
const dist2 = Math.abs(positionsY[yPos] - y);
|
||||
yPos = dist1 < dist2 ? yPos - 1 : yPos;
|
||||
}
|
||||
yPos = 0;
|
||||
// The last row may be incomplete, so use its own x-center array.
|
||||
xArray =
|
||||
yPos === positionsY.length - 1 && positionsLastX.length > 0
|
||||
? positionsLastX
|
||||
: positionsX;
|
||||
// Find the column: the first column whose center is to the right of x,
|
||||
// minus 1, gives the column the cursor is in (or -1 if before column 0).
|
||||
xPos = binarySearchFirstItem(xArray, cx => x < cx) - 1;
|
||||
if (yPos < 0) {
|
||||
// y is above the first row: force drop before the very first thumbnail.
|
||||
if (xPos <= 0) {
|
||||
xPos = -1;
|
||||
}
|
||||
yPos = 0;
|
||||
}
|
||||
// Convert (row, col) to a flat thumbnail index, clamped to
|
||||
// [-1, length-1].
|
||||
index = MathClamp(
|
||||
yPos * positionsX.length + xPos,
|
||||
-1,
|
||||
this._thumbnails.length - 1
|
||||
);
|
||||
}
|
||||
const index = MathClamp(
|
||||
yPos * positionsX.length + xPos,
|
||||
-1,
|
||||
this._thumbnails.length - 1
|
||||
);
|
||||
if (index === lastDraggedOverIndex) {
|
||||
// No change.
|
||||
return null;
|
||||
}
|
||||
this.#lastDraggedOverIndex = index;
|
||||
// Use the last-row gap when the drop slot is in the incomplete last row.
|
||||
const space =
|
||||
yPos === positionsY.length - 1 && positionsLastX.length > 0 && xPos >= 0
|
||||
? lastSpaceBetweenThumbnails
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user