mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 21:25:04 -05:00
feat(ui): more resilient gallery scrollIntoView
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||
import { draggable, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import type { FlexProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Icon, Image } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { AppDispatch, AppGetState } from 'app/store/store';
|
||||
@@ -14,7 +14,6 @@ import { createSingleImageDragPreview, setSingleImageDragPreview } from 'feature
|
||||
import { firefoxDndFix } from 'features/dnd/util';
|
||||
import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||
import { GalleryImageHoverIcons } from 'features/gallery/components/ImageGrid/GalleryImageHoverIcons';
|
||||
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
|
||||
import {
|
||||
selectGetImageNamesQueryArgs,
|
||||
selectSelectedBoardId,
|
||||
@@ -241,13 +240,11 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
|
||||
}, [store]);
|
||||
|
||||
const dataTestId = useMemo(() => getGalleryImageDataTestId(imageDTO.image_name), [imageDTO.image_name]);
|
||||
|
||||
useImageContextMenu(imageDTO, element);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={galleryImageContainerSX} data-testid={dataTestId} data-is-dragging={isDragging}>
|
||||
<Box sx={galleryImageContainerSX} data-is-dragging={isDragging} data-image-name={imageDTO.image_name}>
|
||||
<Flex
|
||||
role="button"
|
||||
className={GALLERY_IMAGE_CLASS}
|
||||
@@ -279,8 +276,8 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
|
||||
GalleryImage.displayName = 'GalleryImage';
|
||||
|
||||
export const GalleryImagePlaceholder = memo(() => (
|
||||
<Flex w="full" h="full" bg="base.850" borderRadius="base" alignItems="center" justifyContent="center">
|
||||
export const GalleryImagePlaceholder = memo((props: FlexProps) => (
|
||||
<Flex w="full" h="full" bg="base.850" borderRadius="base" alignItems="center" justifyContent="center" {...props}>
|
||||
<Icon as={PiImageBold} boxSize={16} color="base.800" />
|
||||
</Flex>
|
||||
));
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const getGalleryImageDataTestId = (imageName?: string) => `gallery-image-${imageName}`;
|
||||
@@ -128,60 +128,69 @@ const getImagesPerRow = (rootEl: HTMLDivElement): number => {
|
||||
* Scroll the item at the given index into view if it is not currently visible.
|
||||
*/
|
||||
const scrollIntoView = (
|
||||
index: number,
|
||||
targetImageName: string,
|
||||
imageNames: string[],
|
||||
rootEl: HTMLDivElement,
|
||||
virtuosoGridHandle: VirtuosoGridHandle,
|
||||
range: ListRange
|
||||
) => {
|
||||
if (range.endIndex === 0) {
|
||||
// No range is rendered; no need to scroll to anything.
|
||||
return;
|
||||
}
|
||||
|
||||
// First get the virtuoso grid list root element
|
||||
const gridList = rootEl.querySelector('.virtuoso-grid-list') as HTMLElement;
|
||||
const targetIndex = imageNames.findIndex((name) => name === targetImageName);
|
||||
|
||||
if (!gridList) {
|
||||
// No grid - cannot scroll!
|
||||
if (targetIndex === -1) {
|
||||
// The image isn't in the currently rendered list.
|
||||
return;
|
||||
}
|
||||
|
||||
// Then find the specific item within the grid list
|
||||
const targetItem = gridList.querySelector(`.virtuoso-grid-item[data-index="${index}"]`) as HTMLElement;
|
||||
const targetItem = rootEl.querySelector(
|
||||
`.virtuoso-grid-item:has([data-image-name="${targetImageName}"])`
|
||||
) as HTMLElement;
|
||||
|
||||
if (!targetItem) {
|
||||
if (index > range.endIndex) {
|
||||
if (targetIndex > range.endIndex) {
|
||||
virtuosoGridHandle.scrollToIndex({
|
||||
index,
|
||||
index: targetIndex,
|
||||
behavior: 'auto',
|
||||
align: 'start',
|
||||
});
|
||||
} else if (index < range.startIndex) {
|
||||
} else if (targetIndex < range.startIndex) {
|
||||
virtuosoGridHandle.scrollToIndex({
|
||||
index,
|
||||
index: targetIndex,
|
||||
behavior: 'auto',
|
||||
align: 'end',
|
||||
});
|
||||
} else {
|
||||
log.warn(`Unable to find item index ${index} but it is in range ${range.startIndex}-${range.endIndex}`);
|
||||
log.debug(
|
||||
`Unable to find image ${targetImageName} at index ${targetIndex} but it is in the rendered range ${range.startIndex}-${range.endIndex}`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We found the image in the DOM, but it might be in the overscan range - rendered but not in the visible viewport.
|
||||
// Check if it is in the viewport and scroll if necessary.
|
||||
|
||||
const itemRect = targetItem.getBoundingClientRect();
|
||||
const rootRect = rootEl.getBoundingClientRect();
|
||||
|
||||
if (itemRect.top < rootRect.top) {
|
||||
virtuosoGridHandle.scrollToIndex({
|
||||
index,
|
||||
index: targetIndex,
|
||||
behavior: 'auto',
|
||||
align: 'start',
|
||||
});
|
||||
} else if (itemRect.bottom > rootRect.bottom) {
|
||||
virtuosoGridHandle.scrollToIndex({
|
||||
index,
|
||||
index: targetIndex,
|
||||
behavior: 'auto',
|
||||
align: 'end',
|
||||
});
|
||||
} else {
|
||||
// Image is already in view
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -377,22 +386,18 @@ const useKeepSelectedImageInView = (
|
||||
rootRef: React.RefObject<HTMLDivElement>,
|
||||
rangeRef: MutableRefObject<ListRange>
|
||||
) => {
|
||||
const imageName = useAppSelector(selectLastSelectedImage);
|
||||
const targetImageName = useAppSelector(selectLastSelectedImage);
|
||||
|
||||
useEffect(() => {
|
||||
const virtuosoGridHandle = virtuosoRef.current;
|
||||
const rootEl = rootRef.current;
|
||||
const range = rangeRef.current;
|
||||
|
||||
if (!virtuosoGridHandle || !rootEl || !imageNames || imageNames.length === 0) {
|
||||
if (!virtuosoGridHandle || !rootEl || !targetImageName || !imageNames || imageNames.length === 0) {
|
||||
return;
|
||||
}
|
||||
const index = imageName ? imageNames.indexOf(imageName) : 0;
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
scrollIntoView(index, rootEl, virtuosoGridHandle, range);
|
||||
}, [imageName, imageNames, rangeRef, rootRef, virtuosoRef]);
|
||||
scrollIntoView(targetImageName, imageNames, rootEl, virtuosoGridHandle, range);
|
||||
}, [targetImageName, imageNames, rangeRef, rootRef, virtuosoRef]);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user