diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 65c15024c..e8214d945 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -1,4 +1,11 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { + ForwardedRef, + forwardRef, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { Button, Col, @@ -69,7 +76,9 @@ const CLASSNAME_FOOTER_RIGHT = `${CLASSNAME_FOOTER}-right`; const CLASSNAME_DISPLAY = `${CLASSNAME}-display`; const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`; const CLASSNAME_INSTANT = `${CLASSNAME_CAROUSEL}-instant`; +const CLASSNAME_SWIPE = `${CLASSNAME_CAROUSEL}-swipe`; const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`; +const CLASSNAME_IMAGE_CONTAINER = `${CLASSNAME_CAROUSEL}-image-container`; const CLASSNAME_NAVBUTTON = `${CLASSNAME}-navbutton`; const CLASSNAME_NAV = `${CLASSNAME}-nav`; const CLASSNAME_NAVIMAGE = `${CLASSNAME_NAV}-image`; @@ -82,6 +91,108 @@ const MIN_ZOOM = 0.1; const SCROLL_ZOOM_TIMEOUT = 250; const ZOOM_NONE_EPSILON = 0.015; +interface ILightboxCarouselProps { + transition: string | null; + currentIndex: number; + images: ILightboxImage[]; + displayMode: GQL.ImageLightboxDisplayMode; + lightboxSettings: GQL.ConfigImageLightboxInput | undefined; + resetPosition?: boolean; + zoom: number; + scrollAttemptsBeforeChange: number; + firstScroll: React.MutableRefObject; + inScrollGroup: React.MutableRefObject; + movingLeft: boolean; + updateZoom: (v: number) => void; + debouncedScrollReset: () => void; + handleLeft: () => void; + handleRight: () => void; + overrideTransition: (t: string) => void; +} + +const LightboxCarousel = forwardRef(function ( + { + transition, + currentIndex, + images, + displayMode, + lightboxSettings, + resetPosition, + zoom, + scrollAttemptsBeforeChange, + firstScroll, + inScrollGroup, + movingLeft, + updateZoom, + debouncedScrollReset, + handleLeft, + handleRight, + overrideTransition, + }: ILightboxCarouselProps, + carouselRef: ForwardedRef +) { + const [carouselShift, setCarouselShift] = useState(0); + + function handleMoveCarousel(delta: number) { + overrideTransition(CLASSNAME_INSTANT); + setCarouselShift(carouselShift + delta); + } + + function handleReleaseCarousel( + event: React.TouchEvent, + swipeDuration: number + ) { + const cappedDuration = Math.max(50, Math.min(500, swipeDuration)) / 1000; + const adjustedShift = carouselShift / (2 * cappedDuration); + if (adjustedShift < -window.innerWidth / 2) { + handleRight(); + } else if (adjustedShift > window.innerWidth / 2) { + handleLeft(); + } + setCarouselShift(0); + overrideTransition(CLASSNAME_SWIPE); + } + + return ( +
+ {images.map((image, i) => ( +
+ {i >= currentIndex - 1 && i <= currentIndex + 1 ? ( + + ) : undefined} +
+ ))} +
+ ); +}); + interface IProps { images: ILightboxImage[]; isVisible: boolean; @@ -117,7 +228,7 @@ export const LightboxComponent: React.FC = ({ const [index, setIndex] = useState(null); const [movingLeft, setMovingLeft] = useState(false); const oldIndex = useRef(null); - const [instantTransition, setInstantTransition] = useState(false); + const [transition, setTransition] = useState(null); const [isSwitchingPage, setIsSwitchingPage] = useState(true); const [isFullscreen, setFullscreen] = useState(false); const [showOptions, setShowOptions] = useState(false); @@ -144,7 +255,6 @@ export const LightboxComponent: React.FC = ({ const containerRef = useRef(null); const overlayTarget = useRef(null); - const carouselRef = useRef(null); const indicatorRef = useRef(null); const navRef = useRef(null); const clearIntervalCallback = useRef<() => void>(); @@ -232,15 +342,15 @@ export const LightboxComponent: React.FC = ({ } }, [isSwitchingPage, images, index]); - const disableInstantTransition = useDebounce( - () => setInstantTransition(false), - 400 - ); + const restoreTransition = useDebounce(() => setTransition(null), 400); - const setInstant = useCallback(() => { - setInstantTransition(true); - disableInstantTransition(); - }, [disableInstantTransition]); + const overrideTransition = useCallback( + (t: string) => { + setTransition(t); + restoreTransition(); + }, + [restoreTransition] + ); useEffect(() => { if (images.length < 2) return; @@ -422,12 +532,12 @@ export const LightboxComponent: React.FC = ({ const handleKey = useCallback( (e: KeyboardEvent) => { if (e.repeat && (e.key === "ArrowRight" || e.key === "ArrowLeft")) - setInstant(); + overrideTransition(CLASSNAME_INSTANT); if (e.key === "ArrowLeft") handleLeft(); else if (e.key === "ArrowRight") handleRight(); else if (e.key === "Escape") close(); }, - [setInstant, handleLeft, handleRight, close] + [overrideTransition, handleLeft, handleRight, close] ); const handleFullScreenChange = () => { if (clearIntervalCallback.current) { @@ -869,45 +979,24 @@ export const LightboxComponent: React.FC = ({ )} - -
- {images.map((image, i) => ( -
- {i >= currentIndex - 1 && i <= currentIndex + 1 ? ( - - ) : undefined} -
- ))} -
- + {allowNavigation && (