fix(ui): fix gallery nav math

- Use the virtuoso grid item container and list containers to calculate imagesPerRow, skipping manual compensation for padding of images
- Round the imagesPerRow instead of flooring - we often will end up with values like 4.99999 due to floating point precision
- Update `getDownImage` comments & logic to be clearer
- Use variables for the ids in query selectors, preventing future typos
- Only scroll if the new selected image is different from the prev one
This commit is contained in:
psychedelicious 2024-01-07 11:58:52 +11:00 committed by Kent Keirsey
parent ffa05a0bb3
commit 75c1c4ce5a
3 changed files with 23 additions and 28 deletions

View File

@ -3,17 +3,15 @@ import { Box, forwardRef } from '@chakra-ui/react';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { memo } from 'react'; import { memo } from 'react';
// This is exported so that we can use it to calculate the number of images per row export const imageItemContainerTestId = 'image-item-container';
// for the directional gallery navigation.
export const GALLERY_IMAGE_PADDING_PX = 6;
type ItemContainerProps = PropsWithChildren & FlexProps; type ItemContainerProps = PropsWithChildren & FlexProps;
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => ( const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
<Box <Box
className="item-container" className="item-container"
ref={ref} ref={ref}
p={`${GALLERY_IMAGE_PADDING_PX}px`} p={1.5}
data-testid="image-item-container" data-testid={imageItemContainerTestId}
> >
{props.children} {props.children}
</Box> </Box>

View File

@ -4,6 +4,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { memo } from 'react'; import { memo } from 'react';
export const imageListContainerTestId = 'image-list-container';
type ListContainerProps = PropsWithChildren & FlexProps; type ListContainerProps = PropsWithChildren & FlexProps;
const ListContainer = forwardRef((props: ListContainerProps, ref) => { const ListContainer = forwardRef((props: ListContainerProps, ref) => {
const galleryImageMinimumWidth = useAppSelector( const galleryImageMinimumWidth = useAppSelector(
@ -16,7 +18,7 @@ const ListContainer = forwardRef((props: ListContainerProps, ref) => {
className="list-container" className="list-container"
ref={ref} ref={ref}
gridTemplateColumns={`repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, 1fr))`} gridTemplateColumns={`repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, 1fr))`}
data-testid="image-list-container" data-testid={imageListContainerTestId}
> >
{props.children} {props.children}
</Grid> </Grid>

View File

@ -1,6 +1,7 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
import { GALLERY_IMAGE_PADDING_PX } from 'features/gallery/components/ImageGrid/ImageGridItemContainer'; import { imageItemContainerTestId } from 'features/gallery/components/ImageGrid/ImageGridItemContainer';
import { imageListContainerTestId } from 'features/gallery/components/ImageGrid/ImageGridListContainer';
import { virtuosoGridRefs } from 'features/gallery/components/ImageGrid/types'; import { virtuosoGridRefs } from 'features/gallery/components/ImageGrid/types';
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages'; import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors'; import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
@ -27,23 +28,17 @@ import { imagesSelectors } from 'services/api/util';
* Gets the number of images per row in the gallery by grabbing their DOM elements. * Gets the number of images per row in the gallery by grabbing their DOM elements.
*/ */
const getImagesPerRow = (): number => { const getImagesPerRow = (): number => {
const imageRect = Object.values( const widthOfGalleryImage =
document.getElementsByClassName('gallerygrid-image') document
)[0]?.getBoundingClientRect(); .querySelector(`[data-testid="${imageItemContainerTestId}"]`)
?.getBoundingClientRect().width ?? 1;
// We have to manually take into account the padding of the image container, else const widthOfGalleryGrid =
// imagesPerRow will be wrong when the gallery is large or images are very small. document
const widthOfGalleryImage = imageRect .querySelector(`[data-testid="${imageListContainerTestId}"]`)
? imageRect.width + GALLERY_IMAGE_PADDING_PX * 2 ?.getBoundingClientRect().width ?? 0;
: 0;
const galleryGridRect = document const imagesPerRow = Math.round(widthOfGalleryGrid / widthOfGalleryImage);
.getElementById('gallery-grid')
?.getBoundingClientRect();
const widthOfGalleryGrid = galleryGridRect?.width ?? 0;
const imagesPerRow = Math.floor(widthOfGalleryGrid / widthOfGalleryImage);
return imagesPerRow; return imagesPerRow;
}; };
@ -104,11 +99,11 @@ const getUpImage = (images: ImageDTO[], currentIndex: number) => {
const getDownImage = (images: ImageDTO[], currentIndex: number) => { const getDownImage = (images: ImageDTO[], currentIndex: number) => {
const imagesPerRow = getImagesPerRow(); const imagesPerRow = getImagesPerRow();
// If we are on the first row, we want to stay on the first row, not go to last image // If there are no images below the current image, we want to stay where we are
const isOnLastRow = currentIndex >= images.length - imagesPerRow; const areImagesBelow = currentIndex < images.length - imagesPerRow;
const index = isOnLastRow const index = areImagesBelow
? currentIndex ? clamp(currentIndex + imagesPerRow, 0, images.length - 1)
: clamp(currentIndex + imagesPerRow, 0, images.length - 1); : currentIndex;
const image = images[index]; const image = images[index];
return { index, image }; return { index, image };
}; };
@ -164,7 +159,7 @@ export const useGalleryNavigation = (): UseGalleryNavigationReturn => {
imagesSelectors.selectAll(data), imagesSelectors.selectAll(data),
lastSelectedImageIndex lastSelectedImageIndex
); );
if (!image) { if (!image || index === lastSelectedImageIndex) {
return; return;
} }
dispatch(imageSelected(image)); dispatch(imageSelected(image));