Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { useImageViewerContext } from './context';
import { NoContentForViewer } from './NoContentForViewer';
import { ProgressImage } from './ProgressImage2';
import { ProgressIndicator } from './ProgressIndicator2';
import { useSwipeNavigation } from './useSwipeNavigation';

export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO: ImageDTO | null }) => {
const shouldShowItemDetails = useAppSelector(selectShouldShowItemDetails);
const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer);
const { onLoadImage, $progressEvent, $progressImage } = useImageViewerContext();
const progressEvent = useStore($progressEvent);
const progressImage = useStore($progressImage);
const { onDragEnd } = useSwipeNavigation();

// Show and hide the next/prev buttons on mouse move
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState<boolean>(false);
Expand All @@ -39,11 +41,17 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO: ImageDTO | nu
const withProgress = shouldShowProgressInViewer && progressImage !== null;

return (
<Flex
<Box
as={motion.div}
drag={imageDTO ? 'x' : false}
dragConstraints={{ left: 0, right: 0 }}
dragElastic={0.2}
onDragEnd={onDragEnd}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
width="full"
height="full"
display="flex"
alignItems="center"
justifyContent="center"
position="relative"
Expand Down Expand Up @@ -96,7 +104,7 @@ export const CurrentImagePreview = memo(({ imageDTO }: { imageDTO: ImageDTO | nu
</Box>
)}
</AnimatePresence>
</Flex>
</Box>
);
});
CurrentImagePreview.displayName = 'CurrentImagePreview';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { clamp } from 'es-toolkit/compat';
import { useGalleryImageNames } from 'features/gallery/components/use-gallery-image-names';
import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { useCallback, useMemo } from 'react';

const SWIPE_THRESHOLD = 50; // Minimum distance in pixels to trigger swipe

export const useSwipeNavigation = () => {
const dispatch = useAppDispatch();
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
const { imageNames, isFetching } = useGalleryImageNames();

const isOnFirstItem = useMemo(
() => (lastSelectedItem ? imageNames.at(0) === lastSelectedItem : false),
[imageNames, lastSelectedItem]
);
const isOnLastItem = useMemo(
() => (lastSelectedItem ? imageNames.at(-1) === lastSelectedItem : false),
[imageNames, lastSelectedItem]
);

const navigateToPrevious = useCallback(() => {
if (isOnFirstItem || isFetching) {
return;
}
const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem) - 1 : 0;
const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
dispatch(imageSelected(n));
}, [dispatch, imageNames, lastSelectedItem, isOnFirstItem, isFetching]);

const navigateToNext = useCallback(() => {
if (isOnLastItem || isFetching) {
return;
}
const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem) + 1 : 0;
const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
dispatch(imageSelected(n));
}, [dispatch, imageNames, lastSelectedItem, isOnLastItem, isFetching]);

const onDragEnd = useCallback(
(_event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number; y: number } }) => {
// Swipe right (positive x) = go to previous image
// Swipe left (negative x) = go to next image
if (info.offset.x > SWIPE_THRESHOLD) {
navigateToPrevious();
} else if (info.offset.x < -SWIPE_THRESHOLD) {
navigateToNext();
}
},
[navigateToPrevious, navigateToNext]
);

return {
onDragEnd,
canNavigatePrevious: !isOnFirstItem,
canNavigateNext: !isOnLastItem,
};
};