(ui): improve loader/fetching state while searching, make search term a string in redux

This commit is contained in:
Mary Hipp 2024-06-30 20:55:18 -04:00 committed by psychedelicious
parent 134d831ebf
commit 20d5c3a8bf
5 changed files with 28 additions and 16 deletions

View File

@ -19,11 +19,10 @@ const GalleryImageGrid = () => {
useGalleryHotkeys(); useGalleryHotkeys();
const { t } = useTranslation(); const { t } = useTranslation();
const queryArgs = useAppSelector(selectListImagesQueryArgs); const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { imageDTOs, isLoading, isFetching, isError } = useListImagesQuery(queryArgs, { const { imageDTOs, isLoading, isError } = useListImagesQuery(queryArgs, {
selectFromResult: ({ data, isLoading, isFetching, isSuccess, isError }) => ({ selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({
imageDTOs: data?.items ?? EMPTY_ARRAY, imageDTOs: data?.items ?? EMPTY_ARRAY,
isLoading, isLoading,
isFetching,
isSuccess, isSuccess,
isError, isError,
}), }),
@ -37,7 +36,7 @@ const GalleryImageGrid = () => {
); );
} }
if (isLoading || isFetching) { if (isLoading) {
return ( return (
<Flex w="full" h="full" alignItems="center" justifyContent="center"> <Flex w="full" h="full" alignItems="center" justifyContent="center">
<IAINoContentFallback label={t('gallery.loading')} icon={PiImageBold} /> <IAINoContentFallback label={t('gallery.loading')} icon={PiImageBold} />

View File

@ -1,17 +1,22 @@
import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; import { IconButton, Input, InputGroup, InputRightElement, Spinner } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { searchTermChanged } from 'features/gallery/store/gallerySlice'; import { searchTermChanged } from 'features/gallery/store/gallerySlice';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi'; import { PiXBold } from 'react-icons/pi';
import { useListImagesQuery } from 'services/api/endpoints/images';
export const GallerySearch = () => { export const GallerySearch = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { searchTerm } = useAppSelector((s) => s.gallery); const { searchTerm } = useAppSelector((s) => s.gallery);
const { t } = useTranslation(); const { t } = useTranslation();
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { isFetching } = useListImagesQuery(queryArgs);
const [searchTermInput, setSearchTermInput] = useState(searchTerm); const [searchTermInput, setSearchTermInput] = useState(searchTerm);
const debouncedSetSearchTerm = useMemo(() => { const debouncedSetSearchTerm = useMemo(() => {
@ -40,17 +45,24 @@ export const GallerySearch = () => {
value={searchTermInput} value={searchTermInput}
onChange={handleChangeInput} onChange={handleChangeInput}
data-testid="image-search-input" data-testid="image-search-input"
disabled={isFetching}
/> />
{searchTermInput && searchTermInput.length && ( {isFetching ? (
<InputRightElement h="full" pe={2}> <InputRightElement h="full" pe={2}>
<IconButton <Spinner size="sm" />
onClick={handleClearInput}
size="sm"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>
</InputRightElement> </InputRightElement>
) : (
searchTermInput.length && (
<InputRightElement h="full" pe={2}>
<IconButton
onClick={handleClearInput}
size="sm"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>
</InputRightElement>
)
)} )}
</InputGroup> </InputGroup>
); );

View File

@ -22,7 +22,7 @@ export const selectListImagesQueryArgs = createMemoizedSelector(
is_intermediate: false, is_intermediate: false,
starred_first: gallery.starredFirst, starred_first: gallery.starredFirst,
order_dir: gallery.orderDir, order_dir: gallery.orderDir,
search_term: gallery.searchTerm, search_term: gallery.searchTerm.length ? gallery.searchTerm : undefined,
} }
: skipToken : skipToken
); );

View File

@ -20,6 +20,7 @@ const initialGalleryState: GalleryState = {
offset: 0, offset: 0,
starredFirst: true, starredFirst: true,
orderDir: 'ASC', orderDir: 'ASC',
searchTerm: '',
isImageViewerOpen: true, isImageViewerOpen: true,
imageToCompare: null, imageToCompare: null,
comparisonMode: 'slider', comparisonMode: 'slider',
@ -118,7 +119,7 @@ export const gallerySlice = createSlice({
orderDirChanged: (state, action: PayloadAction<OrderDir>) => { orderDirChanged: (state, action: PayloadAction<OrderDir>) => {
state.orderDir = action.payload; state.orderDir = action.payload;
}, },
searchTermChanged: (state, action: PayloadAction<string | undefined>) => { searchTermChanged: (state, action: PayloadAction<string>) => {
state.searchTerm = action.payload; state.searchTerm = action.payload;
}, },
}, },

View File

@ -22,7 +22,7 @@ export type GalleryState = {
limit: number; limit: number;
starredFirst: boolean; starredFirst: boolean;
orderDir: OrderDir; orderDir: OrderDir;
searchTerm?: string; searchTerm: string;
alwaysShowImageSizeBadge: boolean; alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null; imageToCompare: ImageDTO | null;
comparisonMode: ComparisonMode; comparisonMode: ComparisonMode;