mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
wip
This commit is contained in:
parent
58cb5fefd0
commit
2990fa23fe
@ -47,7 +47,6 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte
|
||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||
import { addReceivedPageOfImagesListener } from './listeners/receivedPageOfImages';
|
||||
import { addSelectionAddedToBatchListener } from './listeners/selectionAddedToBatch';
|
||||
import {
|
||||
addSessionCanceledFulfilledListener,
|
||||
addSessionCanceledPendingListener,
|
||||
@ -201,7 +200,7 @@ addBoardIdSelectedListener();
|
||||
addReceivedOpenAPISchemaListener();
|
||||
|
||||
// Batches
|
||||
addSelectionAddedToBatchListener();
|
||||
// addSelectionAddedToBatchListener();
|
||||
addAddBoardToBatchListener();
|
||||
|
||||
// DND
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { Box, Flex, Icon } from '@chakra-ui/react';
|
||||
import { FaExclamation } from 'react-icons/fa';
|
||||
|
||||
const IAIErrorLoadingImageFallback = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
'::before': {
|
||||
content: "''",
|
||||
display: 'block',
|
||||
pt: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
bg: 'base.100',
|
||||
color: 'base.500',
|
||||
_dark: {
|
||||
color: 'base.700',
|
||||
bg: 'base.850',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon as={FaExclamation} boxSize={16} opacity={0.7} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIErrorLoadingImageFallback;
|
@ -0,0 +1,30 @@
|
||||
import { Box, Skeleton } from '@chakra-ui/react';
|
||||
|
||||
const IAIFillSkeleton = () => {
|
||||
return (
|
||||
<Skeleton
|
||||
sx={{
|
||||
position: 'relative',
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
'::before': {
|
||||
content: "''",
|
||||
display: 'block',
|
||||
pt: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
}}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIFillSkeleton;
|
@ -1,10 +1,12 @@
|
||||
import { Box, Spinner } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIErrorLoadingImageFallback from 'common/components/IAIErrorLoadingImageFallback';
|
||||
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||
import {
|
||||
batchImageRangeEndSelected,
|
||||
batchImageSelected,
|
||||
@ -32,11 +34,13 @@ type BatchImageProps = {
|
||||
|
||||
const BatchImage = (props: BatchImageProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { imageName } = props;
|
||||
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||
|
||||
const {
|
||||
currentData: imageDTO,
|
||||
isLoading,
|
||||
isError,
|
||||
isSuccess,
|
||||
} = useGetImageDTOQuery(imageName);
|
||||
const selector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||
|
||||
const { isSelected, selectionCount, selection } = useAppSelector(selector);
|
||||
@ -76,8 +80,12 @@ const BatchImage = (props: BatchImageProps) => {
|
||||
}
|
||||
}, [imageDTO, selection, selectionCount]);
|
||||
|
||||
if (!imageDTO) {
|
||||
return <Spinner />;
|
||||
if (isLoading) {
|
||||
return <IAIFillSkeleton />;
|
||||
}
|
||||
|
||||
if (isError || !imageDTO) {
|
||||
return <IAIErrorLoadingImageFallback />;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -108,6 +116,7 @@ const BatchImage = (props: BatchImageProps) => {
|
||||
isUploadDisabled={true}
|
||||
resetTooltip="Remove from batch"
|
||||
withResetIcon
|
||||
thumbnail
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
@ -26,7 +26,6 @@ const selector = createSelector(
|
||||
);
|
||||
|
||||
const BatchGrid = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const rootRef = useRef(null);
|
||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||
|
@ -0,0 +1,102 @@
|
||||
import { Box, Spinner } from '@chakra-ui/react';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaExclamation, FaImage } from 'react-icons/fa';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import BatchImage from 'features/batch/components/BatchImage';
|
||||
import { VirtuosoGrid } from 'react-virtuoso';
|
||||
import { useGetAllBoardImagesForBoardQuery } from 'services/api/endpoints/boardImages';
|
||||
import ItemContainer from './ItemContainer';
|
||||
import ListContainer from './ListContainer';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
(state) => {
|
||||
return {
|
||||
imageNames: state.batch.imageNames,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
type BoardGridProps = {
|
||||
board_id: string;
|
||||
};
|
||||
|
||||
const BoardGrid = (props: BoardGridProps) => {
|
||||
const { board_id } = props;
|
||||
const { data, isLoading, isError, isSuccess } =
|
||||
useGetAllBoardImagesForBoardQuery({
|
||||
board_id,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const rootRef = useRef(null);
|
||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||
const [initialize, osInstance] = useOverlayScrollbars({
|
||||
defer: true,
|
||||
options: {
|
||||
scrollbars: {
|
||||
visibility: 'auto',
|
||||
autoHide: 'leave',
|
||||
autoHideDelay: 1300,
|
||||
theme: 'os-theme-dark',
|
||||
},
|
||||
overflow: { x: 'hidden' },
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const { current: root } = rootRef;
|
||||
if (scroller && root) {
|
||||
initialize({
|
||||
target: root,
|
||||
elements: {
|
||||
viewport: scroller,
|
||||
},
|
||||
});
|
||||
}
|
||||
return () => osInstance()?.destroy();
|
||||
}, [scroller, initialize, osInstance]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <FaExclamation />;
|
||||
}
|
||||
|
||||
if (isSuccess && data.image_names) {
|
||||
return (
|
||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||
<VirtuosoGrid
|
||||
style={{ height: '100%' }}
|
||||
data={data.image_names}
|
||||
components={{
|
||||
Item: ItemContainer,
|
||||
List: ListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, imageName) => (
|
||||
<BatchImage key={imageName} imageName={imageName} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IAINoContentFallback
|
||||
label={t('gallery.noImagesInGallery')}
|
||||
icon={FaImage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BoardGrid);
|
@ -1,4 +1,4 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, Spinner } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
@ -7,8 +7,12 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
|
||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { imageSelected } from '../store/gallerySlice';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import {
|
||||
imageRangeEndSelected,
|
||||
imageSelected,
|
||||
imageSelectionToggled,
|
||||
} from '../store/gallerySlice';
|
||||
import ImageContextMenu from './ImageContextMenu';
|
||||
|
||||
export const makeSelector = (image_name: string) =>
|
||||
@ -23,33 +27,29 @@ export const makeSelector = (image_name: string) =>
|
||||
);
|
||||
|
||||
interface HoverableImageProps {
|
||||
imageDTO: ImageDTO;
|
||||
imageName: string;
|
||||
}
|
||||
|
||||
const GalleryImage = (props: HoverableImageProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { imageDTO } = props;
|
||||
const { image_name } = imageDTO;
|
||||
|
||||
const localSelector = useMemo(() => makeSelector(image_name), [image_name]);
|
||||
const { imageName } = props;
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||
const localSelector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||
|
||||
const { isSelected, selectionCount, selection } =
|
||||
useAppSelector(localSelector);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
// multiselect disabled for now
|
||||
// if (e.shiftKey) {
|
||||
// dispatch(imageRangeEndSelected(props.imageDTO.image_name));
|
||||
// } else if (e.ctrlKey || e.metaKey) {
|
||||
// dispatch(imageSelectionToggled(props.imageDTO.image_name));
|
||||
// } else {
|
||||
// dispatch(imageSelected(props.imageDTO.image_name));
|
||||
// }
|
||||
dispatch(imageSelected(props.imageDTO.image_name));
|
||||
if (e.shiftKey) {
|
||||
dispatch(imageRangeEndSelected(imageName));
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
dispatch(imageSelectionToggled(imageName));
|
||||
} else {
|
||||
dispatch(imageSelected(imageName));
|
||||
}
|
||||
},
|
||||
[dispatch, props.imageDTO.image_name]
|
||||
[dispatch, imageName]
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
@ -81,13 +81,17 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
}
|
||||
}, [imageDTO, selection, selectionCount]);
|
||||
|
||||
if (!imageDTO) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||
<ImageContextMenu imageDTO={imageDTO}>
|
||||
{(ref) => (
|
||||
<Box
|
||||
position="relative"
|
||||
key={image_name}
|
||||
key={imageName}
|
||||
userSelect="none"
|
||||
ref={ref}
|
||||
sx={{
|
||||
|
@ -2,7 +2,6 @@ import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import { MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { selectionAddedToBatch } from 'app/store/middleware/listenerMiddleware/listeners/selectionAddedToBatch';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
@ -10,6 +9,7 @@ import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
||||
import {
|
||||
imageAddedToBatch,
|
||||
imageRemovedFromBatch,
|
||||
imagesAddedToBatch,
|
||||
} from 'features/batch/store/batchSlice';
|
||||
import {
|
||||
resizeAndScaleCanvas,
|
||||
@ -158,7 +158,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
}, [imageDTO.image_url]);
|
||||
|
||||
const handleAddSelectionToBatch = useCallback(() => {
|
||||
dispatch(selectionAddedToBatch({ images_names: selection }));
|
||||
dispatch(imagesAddedToBatch(selection));
|
||||
}, [dispatch, selection]);
|
||||
|
||||
const handleAddToBatch = useCallback(() => {
|
||||
|
@ -38,6 +38,7 @@ import {
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { mode } from 'theme/util/mode';
|
||||
import BatchGrid from './BatchGrid';
|
||||
import BoardGrid from './BoardGrid';
|
||||
import BoardsList from './Boards/BoardsList';
|
||||
import ImageGalleryGrid from './ImageGalleryGrid';
|
||||
|
||||
@ -228,7 +229,13 @@ const ImageGalleryContent = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex direction="column" gap={2} h="full" w="full">
|
||||
{selectedBoardId === 'batch' ? <BatchGrid /> : <ImageGalleryGrid />}
|
||||
{selectedBoardId === 'batch' ? (
|
||||
<BatchGrid />
|
||||
) : selectedBoardId ? (
|
||||
<BoardGrid board_id={selectedBoardId} />
|
||||
) : (
|
||||
<ImageGalleryGrid />
|
||||
)}
|
||||
</Flex>
|
||||
</VStack>
|
||||
);
|
||||
|
@ -160,7 +160,7 @@ const ImageGalleryGrid = () => {
|
||||
) : (
|
||||
<GalleryImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
imageDTO={item}
|
||||
imageName={item.image_name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user