Scroll to next image using lightbox (#2403)

* Scroll at end of image goes to next/previous
* Align bottom image when moving left
This commit is contained in:
WithoutPants 2022-03-22 11:00:32 +11:00 committed by GitHub
parent 5eee305a33
commit 228e8c9bfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 17 deletions

View file

@ -1,4 +1,5 @@
### 🎨 Improvements
* Image lightbox now transitions to next/previous image when scrolling in pan-Y mode. ([#2403](https://github.com/stashapp/stash/pull/2403))
* Allow customisation of UI theme color using `theme_color` property in `config.yml` ([#2365](https://github.com/stashapp/stash/pull/2365))
* Improved autotag performance. ([#2368](https://github.com/stashapp/stash/pull/2368))

View file

@ -89,6 +89,7 @@ export const LightboxComponent: React.FC<IProps> = ({
const [updateImage] = useImageUpdate();
const [index, setIndex] = useState<number | null>(null);
const [movingLeft, setMovingLeft] = useState(false);
const oldIndex = useRef<number | null>(null);
const [instantTransition, setInstantTransition] = useState(false);
const [isSwitchingPage, setIsSwitchingPage] = useState(true);
@ -261,6 +262,8 @@ export const LightboxComponent: React.FC<IProps> = ({
(isUserAction = true) => {
if (isSwitchingPage || index === -1) return;
setMovingLeft(true);
if (index === 0) {
// go to next page, or loop back if no callback is set
if (pageCallback) {
@ -281,6 +284,8 @@ export const LightboxComponent: React.FC<IProps> = ({
(isUserAction = true) => {
if (isSwitchingPage) return;
setMovingLeft(false);
if (index === images.length - 1) {
// go to preview page, or loop back if no callback is set
if (pageCallback) {
@ -685,6 +690,7 @@ export const LightboxComponent: React.FC<IProps> = ({
scrollMode={scrollMode}
onLeft={handleLeft}
onRight={handleRight}
alignBottom={movingLeft}
zoom={i === currentIndex ? zoom : 1}
setZoom={(v) => setZoom(v)}
resetPosition={resetPosition}

View file

@ -24,6 +24,8 @@ interface IProps {
scrollMode: ScrollMode;
resetPosition?: boolean;
zoom: number;
// set to true to align image with bottom instead of top
alignBottom?: boolean;
setZoom: (v: number) => void;
onLeft: () => void;
onRight: () => void;
@ -36,6 +38,7 @@ export const LightboxImage: React.FC<IProps> = ({
displayMode,
scaleUp,
scrollMode,
alignBottom,
zoom,
setZoom,
resetPosition,
@ -132,37 +135,45 @@ export const LightboxImage: React.FC<IProps> = ({
newPositionY = Math.min((boxHeight - height) / 2, 0);
} else {
// otherwise, align top of image with container
newPositionY = Math.min((height * newZoom - height) / 2, 0);
if (!alignBottom) {
newPositionY = Math.min((height * newZoom - height) / 2, 0);
} else {
newPositionY = boxHeight - height * newZoom;
}
}
setDefaultZoom(newZoom);
setPositionX(newPositionX);
setPositionY(newPositionY);
}, [width, height, boxWidth, boxHeight, displayMode, scaleUp]);
}, [width, height, boxWidth, boxHeight, displayMode, scaleUp, alignBottom]);
const calculateTopPosition = useCallback(() => {
const calculateInitialPosition = useCallback(() => {
// Center image from container's center
const newPositionX = Math.min((boxWidth - width) / 2, 0);
let newPositionY: number;
if (zoom * defaultZoom * height > boxHeight) {
newPositionY = (height * zoom * defaultZoom - height) / 2;
if (!alignBottom) {
newPositionY = (height * zoom * defaultZoom - height) / 2;
} else {
newPositionY = boxHeight - height * zoom * defaultZoom;
}
} else {
newPositionY = Math.min((boxHeight - height) / 2, 0);
}
return [newPositionX, newPositionY];
}, [boxWidth, width, boxHeight, height, zoom, defaultZoom]);
}, [boxWidth, width, boxHeight, height, zoom, defaultZoom, alignBottom]);
useEffect(() => {
if (resetPosition !== resetPositionRef.current) {
resetPositionRef.current = resetPosition;
const [x, y] = calculateTopPosition();
const [x, y] = calculateInitialPosition();
setPositionX(x);
setPositionY(y);
}
}, [resetPosition, resetPositionRef, calculateTopPosition]);
}, [resetPosition, resetPositionRef, calculateInitialPosition]);
function getScrollMode(ev: React.WheelEvent<HTMLDivElement>) {
if (ev.shiftKey) {
@ -184,24 +195,60 @@ export const LightboxImage: React.FC<IProps> = ({
}
}
function onImageScroll(ev: React.WheelEvent<HTMLDivElement>) {
const percent = ev.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
const minY = (defaultZoom * height - height) / 2 - defaultZoom * height + 1;
const maxY = (defaultZoom * height - height) / 2 + boxHeight - 1;
function onImageScrollPanY(ev: React.WheelEvent<HTMLDivElement>) {
const appliedZoom = zoom * defaultZoom;
let minY, maxY: number;
const inBounds = zoom * defaultZoom * height <= boxHeight;
// NOTE: I don't even know how these work, but they do
if (!inBounds) {
if (height > boxHeight) {
minY =
(appliedZoom * height - height) / 2 -
appliedZoom * height +
boxHeight;
maxY = (appliedZoom * height - height) / 2;
} else {
minY = (boxHeight - appliedZoom * height) / 2;
maxY = (appliedZoom * height - boxHeight) / 2;
}
} else {
minY = Math.min((boxHeight - height) / 2, 0);
maxY = minY;
}
let newPositionY =
positionY + (ev.deltaY < 0 ? SCROLL_PAN_STEP : -SCROLL_PAN_STEP);
// #2389 - if scroll up and at top, then go to previous image
// if scroll down and at bottom, then go to next image
if (newPositionY > maxY && positionY === maxY) {
onLeft();
} else if (newPositionY < minY && positionY === minY) {
onRight();
} else {
// ensure image doesn't go offscreen
console.log("unconstrained y: " + newPositionY);
newPositionY = Math.max(newPositionY, minY);
newPositionY = Math.min(newPositionY, maxY);
console.log("positionY: " + positionY + " newPositionY: " + newPositionY);
setPositionY(newPositionY);
}
ev.stopPropagation();
}
function onImageScroll(ev: React.WheelEvent<HTMLDivElement>) {
const percent = ev.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
switch (getScrollMode(ev)) {
case ScrollMode.ZOOM:
setZoom(zoom * percent);
break;
case ScrollMode.PAN_Y:
// ensure image doesn't go offscreen
newPositionY = Math.max(newPositionY, minY);
newPositionY = Math.min(newPositionY, maxY);
setPositionY(newPositionY);
ev.stopPropagation();
onImageScrollPanY(ev);
break;
}
}