Skip to content

Commit 5279646

Browse files
Merge pull request #20751 from calixteman/bug2016693
Avoid to scroll too much when the thumbnail is at the bottom of the sidebar (bug 2016693)
2 parents 98d0332 + 4b7fa1c commit 5279646

File tree

1 file changed

+121
-29
lines changed

1 file changed

+121
-29
lines changed

web/pdf_thumbnail_viewer.js

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ class PDFThumbnailViewer {
127127

128128
#isCut = false;
129129

130+
#isOneColumnView = false;
131+
132+
#scrollableContainerWidth = 0;
133+
134+
#scrollableContainerHeight = 0;
135+
130136
/**
131137
* @param {PDFThumbnailViewerOptions} options
132138
*/
@@ -720,18 +726,36 @@ class PDFThumbnailViewer {
720726
}
721727

722728
#moveDraggedContainer(dx, dy) {
723-
this.#draggedImageOffsetX += dx;
724-
this.#draggedImageOffsetY += dy;
729+
if (this.#isOneColumnView) {
730+
dx = 0;
731+
}
732+
if (
733+
this.#draggedImageX + dx < 0 ||
734+
this.#draggedImageX + this.#draggedImageWidth + dx >
735+
this.#scrollableContainerWidth
736+
) {
737+
dx = 0;
738+
}
739+
if (
740+
this.#draggedImageY + dy < 0 ||
741+
this.#draggedImageY + this.#draggedImageHeight + dy >
742+
this.#scrollableContainerHeight
743+
) {
744+
dy = 0;
745+
}
746+
725747
this.#draggedImageX += dx;
726748
this.#draggedImageY += dy;
749+
this.#draggedImageOffsetX += dx;
750+
this.#draggedImageOffsetY += dy;
727751
this.#draggedContainer.style.translate = `${this.#draggedImageOffsetX}px ${this.#draggedImageOffsetY}px`;
728752
if (
729753
this.#draggedImageY + this.#draggedImageHeight >
730754
this.#currentScrollBottom
731755
) {
732756
this.scrollableContainer.scrollTop = Math.min(
733757
this.scrollableContainer.scrollTop + PIXELS_TO_SCROLL_WHEN_DRAGGING,
734-
this.scrollableContainer.scrollHeight
758+
this.#scrollableContainerHeight
735759
);
736760
} else if (this.#draggedImageY < this.#currentScrollTop) {
737761
this.scrollableContainer.scrollTop = Math.max(
@@ -843,6 +867,11 @@ class PDFThumbnailViewer {
843867
lastSpace: (positionsLastX.at(-1) - lastRightX) / 2,
844868
bbox,
845869
};
870+
this.#isOneColumnView = positionsX.length === 1;
871+
({
872+
clientWidth: this.#scrollableContainerWidth,
873+
scrollHeight: this.#scrollableContainerHeight,
874+
} = this.scrollableContainer);
846875
}
847876

848877
#addEventListeners() {
@@ -954,6 +983,7 @@ class PDFThumbnailViewer {
954983
pointerId: dragPointerId,
955984
} = e;
956985
if (
986+
e.button !== 0 || // Skip right click.
957987
this.#pagesMapper.copiedPageNumbers?.length > 0 ||
958988
!isNaN(this.#lastDraggedOverIndex) ||
959989
!draggedImage.classList.contains("thumbnailImageContainer")
@@ -973,11 +1003,18 @@ class PDFThumbnailViewer {
9731003
// same position on the thumbnail, we need to adjust the offset
9741004
// accordingly.
9751005
const scaleFactor = PDFThumbnailViewer.#getScaleFactor(draggedImage);
976-
this.#draggedImageOffsetX =
977-
((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) / scaleFactor;
9781006
this.#draggedImageOffsetY =
9791007
((scaleFactor - 1) * e.layerY + draggedImage.offsetTop) / scaleFactor;
9801008

1009+
if (this.#isOneColumnView) {
1010+
this.#draggedImageOffsetX =
1011+
draggedImage.offsetLeft +
1012+
((scaleFactor - 1) * 0.5 * draggedImage.offsetWidth) / scaleFactor;
1013+
} else {
1014+
this.#draggedImageOffsetX =
1015+
((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) /
1016+
scaleFactor;
1017+
}
9811018
this.#draggedImageX = thumbnail.offsetLeft + this.#draggedImageOffsetX;
9821019
this.#draggedImageY = thumbnail.offsetTop + this.#draggedImageOffsetY;
9831020
this.#draggedImageWidth = draggedImage.offsetWidth / scaleFactor;
@@ -987,16 +1024,16 @@ class PDFThumbnailViewer {
9871024
"pointermove",
9881025
ev => {
9891026
const { clientX: x, clientY: y, pointerId } = ev;
990-
if (
991-
pointerId !== dragPointerId ||
992-
(Math.abs(x - clickX) <= DRAG_THRESHOLD_IN_PIXELS &&
993-
Math.abs(y - clickY) <= DRAG_THRESHOLD_IN_PIXELS)
994-
) {
995-
// Not enough movement to be considered a drag.
996-
return;
997-
}
998-
9991027
if (isNaN(this.#lastDraggedOverIndex)) {
1028+
if (
1029+
pointerId !== dragPointerId ||
1030+
(Math.abs(x - clickX) <= DRAG_THRESHOLD_IN_PIXELS &&
1031+
Math.abs(y - clickY) <= DRAG_THRESHOLD_IN_PIXELS)
1032+
) {
1033+
// Not enough movement to be considered a drag.
1034+
return;
1035+
}
1036+
10001037
// First movement while dragging.
10011038
this.#onStartDragging(thumbnail);
10021039
const stopDragging = (_e, isDropping = false) => {
@@ -1047,6 +1084,18 @@ class PDFThumbnailViewer {
10471084
passive: false,
10481085
signal,
10491086
});
1087+
window.addEventListener(
1088+
"keydown",
1089+
kEv => {
1090+
if (
1091+
kEv.key === "Escape" &&
1092+
!isNaN(this.#lastDraggedOverIndex)
1093+
) {
1094+
stopDragging(kEv);
1095+
}
1096+
},
1097+
{ signal }
1098+
);
10501099
}
10511100

10521101
const dx = x - prevDragX;
@@ -1155,6 +1204,16 @@ class PDFThumbnailViewer {
11551204
}
11561205
}
11571206

1207+
// Given the drag center (x, y), find the drop slot index: the drag marker
1208+
// will be placed after thumbnail[index], or before all thumbnails if index
1209+
// is -1. Returns null when the drop slot hasn't changed (no marker update
1210+
// needed), or [index, space] where space is the gap (in px) between
1211+
// thumbnails at that slot, used to position the marker.
1212+
//
1213+
// positionsX holds the x-center of each column, positionsY the y-center of
1214+
// each row. positionsLastX holds the x-centers for an incomplete last row
1215+
// (when the total number of thumbnails is not a multiple of the column
1216+
// count).
11581217
#findClosestThumbnail(x, y) {
11591218
if (!this.#thumbnailsPositions) {
11601219
this.#computeThumbnailsPosition();
@@ -1167,6 +1226,9 @@ class PDFThumbnailViewer {
11671226
lastSpace: lastSpaceBetweenThumbnails,
11681227
} = this.#thumbnailsPositions;
11691228
const lastDraggedOverIndex = this.#lastDraggedOverIndex;
1229+
1230+
// Fast-path: reconstruct the row/col of the previous drop slot and check
1231+
// whether (x, y) still falls inside the same cell's bounds.
11701232
let xPos = lastDraggedOverIndex % positionsX.length;
11711233
let yPos = Math.floor(lastDraggedOverIndex / positionsX.length);
11721234
let xArray = yPos === positionsY.length - 1 ? positionsLastX : positionsX;
@@ -1180,28 +1242,58 @@ class PDFThumbnailViewer {
11801242
return null;
11811243
}
11821244

1183-
yPos = binarySearchFirstItem(positionsY, cy => y < cy) - 1;
1184-
xArray =
1185-
yPos === positionsY.length - 1 && positionsLastX.length > 0
1186-
? positionsLastX
1187-
: positionsX;
1188-
xPos = Math.max(0, binarySearchFirstItem(xArray, cx => x < cx) - 1);
1189-
if (yPos < 0) {
1190-
if (xPos <= 0) {
1191-
xPos = -1;
1245+
let index;
1246+
// binarySearchFirstItem returns the first row index whose center is below
1247+
// y, i.e. the first i such that positionsY[i] > y.
1248+
yPos = binarySearchFirstItem(positionsY, cy => y < cy);
1249+
if (this.#isOneColumnView) {
1250+
// In a single column the drop slot is simply the row boundary: the marker
1251+
// goes after row (yPos - 1), meaning before row yPos. index = -1 when y
1252+
// is above the first thumbnail's center (drop before thumbnail 0).
1253+
index = yPos - 1;
1254+
} else {
1255+
// Grid layout: first pick the nearest row, then the nearest column.
1256+
1257+
if (yPos === positionsY.length) {
1258+
// y is below the last row's center — clamp to the last row.
1259+
yPos = positionsY.length - 1;
1260+
} else {
1261+
// Choose between the row just above (yPos - 1) and the row at yPos by
1262+
// comparing distances, so the marker snaps to whichever row center is
1263+
// closer to y.
1264+
const dist1 = Math.abs(positionsY[yPos - 1] - y);
1265+
const dist2 = Math.abs(positionsY[yPos] - y);
1266+
yPos = dist1 < dist2 ? yPos - 1 : yPos;
11921267
}
1193-
yPos = 0;
1268+
// The last row may be incomplete, so use its own x-center array.
1269+
xArray =
1270+
yPos === positionsY.length - 1 && positionsLastX.length > 0
1271+
? positionsLastX
1272+
: positionsX;
1273+
// Find the column: the first column whose center is to the right of x,
1274+
// minus 1, gives the column the cursor is in (or -1 if before column 0).
1275+
xPos = binarySearchFirstItem(xArray, cx => x < cx) - 1;
1276+
if (yPos < 0) {
1277+
// y is above the first row: force drop before the very first thumbnail.
1278+
if (xPos <= 0) {
1279+
xPos = -1;
1280+
}
1281+
yPos = 0;
1282+
}
1283+
// Convert (row, col) to a flat thumbnail index, clamped to
1284+
// [-1, length-1].
1285+
index = MathClamp(
1286+
yPos * positionsX.length + xPos,
1287+
-1,
1288+
this._thumbnails.length - 1
1289+
);
11941290
}
1195-
const index = MathClamp(
1196-
yPos * positionsX.length + xPos,
1197-
-1,
1198-
this._thumbnails.length - 1
1199-
);
12001291
if (index === lastDraggedOverIndex) {
12011292
// No change.
12021293
return null;
12031294
}
12041295
this.#lastDraggedOverIndex = index;
1296+
// Use the last-row gap when the drop slot is in the incomplete last row.
12051297
const space =
12061298
yPos === positionsY.length - 1 && positionsLastX.length > 0 && xPos >= 0
12071299
? lastSpaceBetweenThumbnails

0 commit comments

Comments
 (0)