mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-27 10:35:48 +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;
|
#isCut = false;
|
||||||
|
|
||||||
|
#isOneColumnView = false;
|
||||||
|
|
||||||
|
#scrollableContainerWidth = 0;
|
||||||
|
|
||||||
|
#scrollableContainerHeight = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFThumbnailViewerOptions} options
|
* @param {PDFThumbnailViewerOptions} options
|
||||||
*/
|
*/
|
||||||
@ -716,10 +722,28 @@ class PDFThumbnailViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#moveDraggedContainer(dx, dy) {
|
#moveDraggedContainer(dx, dy) {
|
||||||
this.#draggedImageOffsetX += dx;
|
if (this.#isOneColumnView) {
|
||||||
this.#draggedImageOffsetY += dy;
|
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.#draggedImageX += dx;
|
||||||
this.#draggedImageY += dy;
|
this.#draggedImageY += dy;
|
||||||
|
this.#draggedImageOffsetX += dx;
|
||||||
|
this.#draggedImageOffsetY += dy;
|
||||||
this.#draggedContainer.style.translate = `${this.#draggedImageOffsetX}px ${this.#draggedImageOffsetY}px`;
|
this.#draggedContainer.style.translate = `${this.#draggedImageOffsetX}px ${this.#draggedImageOffsetY}px`;
|
||||||
if (
|
if (
|
||||||
this.#draggedImageY + this.#draggedImageHeight >
|
this.#draggedImageY + this.#draggedImageHeight >
|
||||||
@ -727,7 +751,7 @@ class PDFThumbnailViewer {
|
|||||||
) {
|
) {
|
||||||
this.scrollableContainer.scrollTop = Math.min(
|
this.scrollableContainer.scrollTop = Math.min(
|
||||||
this.scrollableContainer.scrollTop + PIXELS_TO_SCROLL_WHEN_DRAGGING,
|
this.scrollableContainer.scrollTop + PIXELS_TO_SCROLL_WHEN_DRAGGING,
|
||||||
this.scrollableContainer.scrollHeight
|
this.#scrollableContainerHeight
|
||||||
);
|
);
|
||||||
} else if (this.#draggedImageY < this.#currentScrollTop) {
|
} else if (this.#draggedImageY < this.#currentScrollTop) {
|
||||||
this.scrollableContainer.scrollTop = Math.max(
|
this.scrollableContainer.scrollTop = Math.max(
|
||||||
@ -839,6 +863,11 @@ class PDFThumbnailViewer {
|
|||||||
lastSpace: (positionsLastX.at(-1) - lastRightX) / 2,
|
lastSpace: (positionsLastX.at(-1) - lastRightX) / 2,
|
||||||
bbox,
|
bbox,
|
||||||
};
|
};
|
||||||
|
this.#isOneColumnView = positionsX.length === 1;
|
||||||
|
({
|
||||||
|
clientWidth: this.#scrollableContainerWidth,
|
||||||
|
scrollHeight: this.#scrollableContainerHeight,
|
||||||
|
} = this.scrollableContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#addEventListeners() {
|
#addEventListeners() {
|
||||||
@ -950,6 +979,7 @@ class PDFThumbnailViewer {
|
|||||||
pointerId: dragPointerId,
|
pointerId: dragPointerId,
|
||||||
} = e;
|
} = e;
|
||||||
if (
|
if (
|
||||||
|
e.button !== 0 || // Skip right click.
|
||||||
this.#pagesMapper.copiedPageNumbers?.length > 0 ||
|
this.#pagesMapper.copiedPageNumbers?.length > 0 ||
|
||||||
!isNaN(this.#lastDraggedOverIndex) ||
|
!isNaN(this.#lastDraggedOverIndex) ||
|
||||||
!draggedImage.classList.contains("thumbnailImageContainer")
|
!draggedImage.classList.contains("thumbnailImageContainer")
|
||||||
@ -969,11 +999,18 @@ class PDFThumbnailViewer {
|
|||||||
// same position on the thumbnail, we need to adjust the offset
|
// same position on the thumbnail, we need to adjust the offset
|
||||||
// accordingly.
|
// accordingly.
|
||||||
const scaleFactor = PDFThumbnailViewer.#getScaleFactor(draggedImage);
|
const scaleFactor = PDFThumbnailViewer.#getScaleFactor(draggedImage);
|
||||||
this.#draggedImageOffsetX =
|
|
||||||
((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) / scaleFactor;
|
|
||||||
this.#draggedImageOffsetY =
|
this.#draggedImageOffsetY =
|
||||||
((scaleFactor - 1) * e.layerY + draggedImage.offsetTop) / scaleFactor;
|
((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.#draggedImageX = thumbnail.offsetLeft + this.#draggedImageOffsetX;
|
||||||
this.#draggedImageY = thumbnail.offsetTop + this.#draggedImageOffsetY;
|
this.#draggedImageY = thumbnail.offsetTop + this.#draggedImageOffsetY;
|
||||||
this.#draggedImageWidth = draggedImage.offsetWidth / scaleFactor;
|
this.#draggedImageWidth = draggedImage.offsetWidth / scaleFactor;
|
||||||
@ -983,16 +1020,16 @@ class PDFThumbnailViewer {
|
|||||||
"pointermove",
|
"pointermove",
|
||||||
ev => {
|
ev => {
|
||||||
const { clientX: x, clientY: y, pointerId } = 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 (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.
|
// First movement while dragging.
|
||||||
this.#onStartDragging(thumbnail);
|
this.#onStartDragging(thumbnail);
|
||||||
const stopDragging = (_e, isDropping = false) => {
|
const stopDragging = (_e, isDropping = false) => {
|
||||||
@ -1043,6 +1080,18 @@ class PDFThumbnailViewer {
|
|||||||
passive: false,
|
passive: false,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
window.addEventListener(
|
||||||
|
"keydown",
|
||||||
|
kEv => {
|
||||||
|
if (
|
||||||
|
kEv.key === "Escape" &&
|
||||||
|
!isNaN(this.#lastDraggedOverIndex)
|
||||||
|
) {
|
||||||
|
stopDragging(kEv);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dx = x - prevDragX;
|
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) {
|
#findClosestThumbnail(x, y) {
|
||||||
if (!this.#thumbnailsPositions) {
|
if (!this.#thumbnailsPositions) {
|
||||||
this.#computeThumbnailsPosition();
|
this.#computeThumbnailsPosition();
|
||||||
@ -1163,6 +1222,9 @@ class PDFThumbnailViewer {
|
|||||||
lastSpace: lastSpaceBetweenThumbnails,
|
lastSpace: lastSpaceBetweenThumbnails,
|
||||||
} = this.#thumbnailsPositions;
|
} = this.#thumbnailsPositions;
|
||||||
const lastDraggedOverIndex = this.#lastDraggedOverIndex;
|
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 xPos = lastDraggedOverIndex % positionsX.length;
|
||||||
let yPos = Math.floor(lastDraggedOverIndex / positionsX.length);
|
let yPos = Math.floor(lastDraggedOverIndex / positionsX.length);
|
||||||
let xArray = yPos === positionsY.length - 1 ? positionsLastX : positionsX;
|
let xArray = yPos === positionsY.length - 1 ? positionsLastX : positionsX;
|
||||||
@ -1176,28 +1238,58 @@ class PDFThumbnailViewer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
yPos = binarySearchFirstItem(positionsY, cy => y < cy) - 1;
|
let index;
|
||||||
xArray =
|
// binarySearchFirstItem returns the first row index whose center is below
|
||||||
yPos === positionsY.length - 1 && positionsLastX.length > 0
|
// y, i.e. the first i such that positionsY[i] > y.
|
||||||
? positionsLastX
|
yPos = binarySearchFirstItem(positionsY, cy => y < cy);
|
||||||
: positionsX;
|
if (this.#isOneColumnView) {
|
||||||
xPos = Math.max(0, binarySearchFirstItem(xArray, cx => x < cx) - 1);
|
// In a single column the drop slot is simply the row boundary: the marker
|
||||||
if (yPos < 0) {
|
// goes after row (yPos - 1), meaning before row yPos. index = -1 when y
|
||||||
if (xPos <= 0) {
|
// is above the first thumbnail's center (drop before thumbnail 0).
|
||||||
xPos = -1;
|
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) {
|
if (index === lastDraggedOverIndex) {
|
||||||
// No change.
|
// No change.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.#lastDraggedOverIndex = index;
|
this.#lastDraggedOverIndex = index;
|
||||||
|
// Use the last-row gap when the drop slot is in the incomplete last row.
|
||||||
const space =
|
const space =
|
||||||
yPos === positionsY.length - 1 && positionsLastX.length > 0 && xPos >= 0
|
yPos === positionsY.length - 1 && positionsLastX.length > 0 && xPos >= 0
|
||||||
? lastSpaceBetweenThumbnails
|
? lastSpaceBetweenThumbnails
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user