mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
4 Commits
next-test-
...
maryhipp/r
Author | SHA1 | Date | |
---|---|---|---|
6be3da4e71 | |||
067d5b22b2 | |||
cc88f5cc3f | |||
29f7e4a5d1 |
@ -1,11 +1,5 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import {
|
import { isLoadingChanged } from 'features/gallery/store/gallerySlice';
|
||||||
ASSETS_CATEGORIES,
|
|
||||||
IMAGE_CATEGORIES,
|
|
||||||
INITIAL_IMAGE_LIMIT,
|
|
||||||
isLoadingChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
export const appStarted = createAction('app/appStarted');
|
export const appStarted = createAction('app/appStarted');
|
||||||
@ -19,25 +13,6 @@ export const addAppStartedListener = () => {
|
|||||||
) => {
|
) => {
|
||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
// fill up the gallery tab with images
|
|
||||||
await dispatch(
|
|
||||||
receivedPageOfImages({
|
|
||||||
categories: IMAGE_CATEGORIES,
|
|
||||||
is_intermediate: false,
|
|
||||||
offset: 0,
|
|
||||||
limit: INITIAL_IMAGE_LIMIT,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// fill up the assets tab with images
|
|
||||||
await dispatch(
|
|
||||||
receivedPageOfImages({
|
|
||||||
categories: ASSETS_CATEGORIES,
|
|
||||||
is_intermediate: false,
|
|
||||||
offset: 0,
|
|
||||||
limit: INITIAL_IMAGE_LIMIT,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(isLoadingChanged(false));
|
dispatch(isLoadingChanged(false));
|
||||||
},
|
},
|
||||||
|
@ -16,33 +16,26 @@ import {
|
|||||||
ASSETS_CATEGORIES,
|
ASSETS_CATEGORIES,
|
||||||
IMAGE_CATEGORIES,
|
IMAGE_CATEGORIES,
|
||||||
IMAGE_LIMIT,
|
IMAGE_LIMIT,
|
||||||
selectImagesAll,
|
INITIAL_IMAGE_LIMIT,
|
||||||
} from 'features/gallery//store/gallerySlice';
|
} from 'features/gallery//store/gallerySlice';
|
||||||
import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
|
|
||||||
import { VirtuosoGrid } from 'react-virtuoso';
|
import { VirtuosoGrid } from 'react-virtuoso';
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||||
import ImageGridItemContainer from './ImageGridItemContainer';
|
import ImageGridItemContainer from './ImageGridItemContainer';
|
||||||
import ImageGridListContainer from './ImageGridListContainer';
|
import ImageGridListContainer from './ImageGridListContainer';
|
||||||
import { useListBoardImagesQuery } from '../../../../services/api/endpoints/boardImages';
|
import {
|
||||||
|
imagesApi,
|
||||||
|
useListImagesQuery,
|
||||||
|
} from '../../../../services/api/endpoints/images';
|
||||||
|
import { ImageDTO } from '../../../../services/api/types';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector, selectFilteredImages],
|
[stateSelector],
|
||||||
(state, filteredImages) => {
|
(state) => {
|
||||||
const {
|
const { selectedBoardId, galleryView } = state.gallery;
|
||||||
galleryImageMinimumWidth,
|
|
||||||
selectedBoardId,
|
|
||||||
galleryView,
|
|
||||||
total,
|
|
||||||
isLoading,
|
|
||||||
} = state.gallery;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
imageNames: filteredImages.map((i) => i.image_name),
|
|
||||||
total,
|
|
||||||
selectedBoardId,
|
selectedBoardId,
|
||||||
galleryView,
|
galleryView,
|
||||||
galleryImageMinimumWidth,
|
|
||||||
isLoading,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -53,6 +46,10 @@ const GalleryImageGrid = () => {
|
|||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const emptyGalleryRef = useRef<HTMLDivElement>(null);
|
const emptyGalleryRef = useRef<HTMLDivElement>(null);
|
||||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
const [limit, setLimit] = useState(INITIAL_IMAGE_LIMIT);
|
||||||
|
const [imageList, setImageList] = useState<ImageDTO[]>([]);
|
||||||
|
const [isLoadingMore, setIsLoadingMore] = useState(true);
|
||||||
const [initialize, osInstance] = useOverlayScrollbars({
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
defer: true,
|
defer: true,
|
||||||
options: {
|
options: {
|
||||||
@ -66,97 +63,90 @@ const GalleryImageGrid = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [didInitialFetch, setDidInitialFetch] = useState(false);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const {
|
const { selectedBoardId, galleryView } = useAppSelector(selector);
|
||||||
galleryImageMinimumWidth,
|
|
||||||
imageNames: imageNamesAll, //all images names loaded on main tab,
|
|
||||||
total: totalAll,
|
|
||||||
selectedBoardId,
|
|
||||||
galleryView,
|
|
||||||
isLoading: isLoadingAll,
|
|
||||||
} = useAppSelector(selector);
|
|
||||||
|
|
||||||
const { data: imagesForBoard, isLoading: isLoadingImagesForBoard } =
|
useEffect(() => {
|
||||||
useListBoardImagesQuery(
|
setImageList([]);
|
||||||
{ board_id: selectedBoardId },
|
setOffset(0);
|
||||||
{ skip: selectedBoardId === 'all' }
|
setLimit(INITIAL_IMAGE_LIMIT);
|
||||||
);
|
}, [selectedBoardId]);
|
||||||
|
|
||||||
const imageNames = useMemo(() => {
|
const { data: imageListResponse, isLoading: isLoading } = useListImagesQuery({
|
||||||
if (selectedBoardId === 'all') {
|
categories: galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
||||||
return imageNamesAll; // already sorted by images/uploads in gallery selector
|
is_intermediate: false,
|
||||||
} else {
|
offset: 0,
|
||||||
const categories =
|
limit: INITIAL_IMAGE_LIMIT,
|
||||||
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
|
...(selectedBoardId === 'all' ? {} : { board_id: selectedBoardId }),
|
||||||
const imageList = (imagesForBoard?.items || []).filter((img) =>
|
});
|
||||||
categories.includes(img.image_category)
|
|
||||||
|
const { data: paginatedData } = useListImagesQuery(
|
||||||
|
{
|
||||||
|
categories:
|
||||||
|
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
||||||
|
is_intermediate: false,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
...(selectedBoardId === 'all' ? {} : { board_id: selectedBoardId }),
|
||||||
|
},
|
||||||
|
{ skip: offset === 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (imageListResponse) setImageList(imageListResponse.items);
|
||||||
|
}, [imageListResponse]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paginatedData) {
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.updateQueryData(
|
||||||
|
'listImages',
|
||||||
|
{
|
||||||
|
categories:
|
||||||
|
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
||||||
|
is_intermediate: false,
|
||||||
|
offset: 0,
|
||||||
|
limit: INITIAL_IMAGE_LIMIT,
|
||||||
|
...(selectedBoardId === 'all' ? {} : { board_id: selectedBoardId }),
|
||||||
|
},
|
||||||
|
(draftPosts) => {
|
||||||
|
paginatedData.items.forEach((item) => {
|
||||||
|
draftPosts.items.push(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return imageList.map((img) => img.image_name);
|
|
||||||
}
|
}
|
||||||
}, [selectedBoardId, galleryView, imagesForBoard, imageNamesAll]);
|
//eslint-disable-next-line
|
||||||
|
}, [paginatedData, dispatch]);
|
||||||
|
|
||||||
const areMoreAvailable = useMemo(() => {
|
const areMoreAvailable = useMemo(() => {
|
||||||
return selectedBoardId === 'all' ? totalAll > imageNamesAll.length : false;
|
if (imageListResponse?.total) {
|
||||||
}, [selectedBoardId, imageNamesAll.length, totalAll]);
|
return imageListResponse?.total > imageList.length;
|
||||||
|
}
|
||||||
const isLoading = useMemo(() => {
|
}, [imageListResponse?.total, imageList.length]);
|
||||||
return selectedBoardId === 'all' ? isLoadingAll : isLoadingImagesForBoard;
|
|
||||||
}, [selectedBoardId, isLoadingAll, isLoadingImagesForBoard]);
|
|
||||||
|
|
||||||
const handleLoadMoreImages = useCallback(() => {
|
const handleLoadMoreImages = useCallback(() => {
|
||||||
dispatch(
|
setOffset(imageList.length);
|
||||||
receivedPageOfImages({
|
setLimit(IMAGE_LIMIT);
|
||||||
categories:
|
}, [imageList.length]);
|
||||||
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES,
|
|
||||||
is_intermediate: false,
|
|
||||||
offset: imageNames.length,
|
|
||||||
limit: IMAGE_LIMIT,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [dispatch, imageNames.length, galleryView]);
|
|
||||||
|
|
||||||
const handleEndReached = useMemo(() => {
|
useEffect(() => {
|
||||||
if (areMoreAvailable) {
|
// Set up gallery scroler
|
||||||
return handleLoadMoreImages;
|
const { current: root } = rootRef;
|
||||||
|
if (scroller && root) {
|
||||||
|
initialize({
|
||||||
|
target: root,
|
||||||
|
elements: {
|
||||||
|
viewport: scroller,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return undefined;
|
return () => osInstance()?.destroy();
|
||||||
}, [areMoreAvailable, handleLoadMoreImages]);
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
// useEffect(() => {
|
if (!isLoading && imageList.length === 0) {
|
||||||
// if (!didInitialFetch) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// // rough, conservative calculation of how many images fit in the gallery
|
|
||||||
// // TODO: this gets an incorrect value on first load...
|
|
||||||
// const galleryHeight = rootRef.current?.clientHeight ?? 0;
|
|
||||||
// const galleryWidth = rootRef.current?.clientHeight ?? 0;
|
|
||||||
|
|
||||||
// const rows = galleryHeight / galleryImageMinimumWidth;
|
|
||||||
// const columns = galleryWidth / galleryImageMinimumWidth;
|
|
||||||
|
|
||||||
// const imagesToLoad = Math.ceil(rows * columns);
|
|
||||||
|
|
||||||
// setDidInitialFetch(true);
|
|
||||||
|
|
||||||
// // load up that many images
|
|
||||||
// dispatch(
|
|
||||||
// receivedPageOfImages({
|
|
||||||
// offset: 0,
|
|
||||||
// limit: 10,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// }, [
|
|
||||||
// didInitialFetch,
|
|
||||||
// dispatch,
|
|
||||||
// galleryImageMinimumWidth,
|
|
||||||
// galleryView,
|
|
||||||
// selectedBoardId,
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
if (!isLoading && imageNames.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<Box ref={emptyGalleryRef} sx={{ w: 'full', h: 'full' }}>
|
<Box ref={emptyGalleryRef} sx={{ w: 'full', h: 'full' }}>
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
@ -166,22 +156,24 @@ const GalleryImageGrid = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log({ selectedBoardId });
|
|
||||||
|
|
||||||
if (status !== 'rejected') {
|
if (!isLoading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data={imageNames}
|
data={imageList}
|
||||||
components={{
|
components={{
|
||||||
Item: ImageGridItemContainer,
|
Item: ImageGridItemContainer,
|
||||||
List: ImageGridListContainer,
|
List: ImageGridListContainer,
|
||||||
}}
|
}}
|
||||||
scrollerRef={setScroller}
|
scrollerRef={setScroller}
|
||||||
itemContent={(index, imageName) => (
|
itemContent={(index, image) => (
|
||||||
<GalleryImage key={imageName} imageName={imageName} />
|
<GalleryImage
|
||||||
|
key={image.image_name}
|
||||||
|
imageName={image.image_name}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { OffsetPaginatedResults_ImageDTO_ } from 'services/api/types';
|
import { api } from '..';
|
||||||
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
|
||||||
import { paths } from '../schema';
|
import { paths } from '../schema';
|
||||||
|
|
||||||
type ListBoardImagesArg =
|
|
||||||
paths['/api/v1/board_images/{board_id}']['get']['parameters']['path'] &
|
|
||||||
paths['/api/v1/board_images/{board_id}']['get']['parameters']['query'];
|
|
||||||
|
|
||||||
type AddImageToBoardArg =
|
type AddImageToBoardArg =
|
||||||
paths['/api/v1/board_images/']['post']['requestBody']['content']['application/json'];
|
paths['/api/v1/board_images/']['post']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -14,37 +9,6 @@ type RemoveImageFromBoardArg =
|
|||||||
|
|
||||||
export const boardImagesApi = api.injectEndpoints({
|
export const boardImagesApi = api.injectEndpoints({
|
||||||
endpoints: (build) => ({
|
endpoints: (build) => ({
|
||||||
/**
|
|
||||||
* Board Images Queries
|
|
||||||
*/
|
|
||||||
|
|
||||||
listBoardImages: build.query<
|
|
||||||
OffsetPaginatedResults_ImageDTO_,
|
|
||||||
ListBoardImagesArg
|
|
||||||
>({
|
|
||||||
query: ({ board_id, offset, limit }) => ({
|
|
||||||
url: `board_images/${board_id}`,
|
|
||||||
method: 'GET',
|
|
||||||
|
|
||||||
}),
|
|
||||||
providesTags: (result, error, arg) => {
|
|
||||||
// any list of boardimages
|
|
||||||
const tags: ApiFullTagDescription[] = [{ id: 'BoardImage', type: `${arg.board_id}_${LIST_TAG}` }];
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
// and individual tags for each boardimage
|
|
||||||
tags.push(
|
|
||||||
...result.items.map(({ board_id, image_name }) => ({
|
|
||||||
type: 'BoardImage' as const,
|
|
||||||
id: `${board_id}_${image_name}`,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Board Images Mutations
|
* Board Images Mutations
|
||||||
*/
|
*/
|
||||||
@ -77,6 +41,5 @@ export const boardImagesApi = api.injectEndpoints({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useAddImageToBoardMutation,
|
useAddImageToBoardMutation,
|
||||||
useRemoveImageFromBoardMutation,
|
useRemoveImageFromBoardMutation
|
||||||
useListBoardImagesQuery,
|
|
||||||
} = boardImagesApi;
|
} = boardImagesApi;
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { ApiFullTagDescription, api } from '..';
|
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
||||||
import { components } from '../schema';
|
import { components, paths } from '../schema';
|
||||||
import { ImageDTO } from '../types';
|
import { ImageDTO, OffsetPaginatedResults_ImageDTO_ } from '../types';
|
||||||
|
|
||||||
|
type ListImagesArg = NonNullable<
|
||||||
|
paths['/api/v1/images/']['get']['parameters']['query']
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an unsafe type; the object inside is not guaranteed to be valid.
|
* This is an unsafe type; the object inside is not guaranteed to be valid.
|
||||||
@ -15,6 +19,34 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
/**
|
/**
|
||||||
* Image Queries
|
* Image Queries
|
||||||
*/
|
*/
|
||||||
|
listImages: build.query<OffsetPaginatedResults_ImageDTO_, ListImagesArg>({
|
||||||
|
query: (arg) => ({ url: 'images/', params: arg }),
|
||||||
|
providesTags: (result, error, arg) => {
|
||||||
|
// any list of images
|
||||||
|
const tags: ApiFullTagDescription[] = [{ id: 'Image', type: LIST_TAG }];
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
// and individual tags for each image
|
||||||
|
tags.push(
|
||||||
|
...result.items.map(({ image_name, board_id }) => ({
|
||||||
|
type: 'Image' as const,
|
||||||
|
id: image_name,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result && arg.board_id) {
|
||||||
|
tags.push(
|
||||||
|
...result.items.map(({ image_name, board_id }) => ({
|
||||||
|
type: 'BoardImage' as const,
|
||||||
|
id: `${image_name}_${board_id}`,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
}),
|
||||||
getImageDTO: build.query<ImageDTO, string>({
|
getImageDTO: build.query<ImageDTO, string>({
|
||||||
query: (image_name) => ({ url: `images/${image_name}` }),
|
query: (image_name) => ({ url: `images/${image_name}` }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
@ -39,4 +71,4 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { useGetImageDTOQuery, useGetImageMetadataQuery } = imagesApi;
|
export const { useGetImageDTOQuery, useGetImageMetadataQuery, useListImagesQuery } = imagesApi;
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
createApi,
|
createApi,
|
||||||
fetchBaseQuery,
|
fetchBaseQuery,
|
||||||
} from '@reduxjs/toolkit/query/react';
|
} from '@reduxjs/toolkit/query/react';
|
||||||
|
import queryString from 'query-string';
|
||||||
import { $authToken, $baseUrl } from 'services/api/client';
|
import { $authToken, $baseUrl } from 'services/api/client';
|
||||||
|
|
||||||
export const tagTypes = ['Board', 'Image', 'ImageMetadata', 'Model'];
|
export const tagTypes = ['Board', 'Image', 'ImageMetadata', 'Model'];
|
||||||
@ -31,6 +32,9 @@ const dynamicBaseQuery: BaseQueryFn<
|
|||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
},
|
},
|
||||||
|
paramsSerializer: (params: Record<string, any>) => {
|
||||||
|
return queryString.stringify(params, { arrayFormat: 'none' })
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawBaseQuery(args, api, extraOptions);
|
return rawBaseQuery(args, api, extraOptions);
|
||||||
|
Reference in New Issue
Block a user