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 { addModelSelectedListener } from './listeners/modelSelected';
|
||||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||||
import { addReceivedPageOfImagesListener } from './listeners/receivedPageOfImages';
|
import { addReceivedPageOfImagesListener } from './listeners/receivedPageOfImages';
|
||||||
import { addSelectionAddedToBatchListener } from './listeners/selectionAddedToBatch';
|
|
||||||
import {
|
import {
|
||||||
addSessionCanceledFulfilledListener,
|
addSessionCanceledFulfilledListener,
|
||||||
addSessionCanceledPendingListener,
|
addSessionCanceledPendingListener,
|
||||||
@ -201,7 +200,7 @@ addBoardIdSelectedListener();
|
|||||||
addReceivedOpenAPISchemaListener();
|
addReceivedOpenAPISchemaListener();
|
||||||
|
|
||||||
// Batches
|
// Batches
|
||||||
addSelectionAddedToBatchListener();
|
// addSelectionAddedToBatchListener();
|
||||||
addAddBoardToBatchListener();
|
addAddBoardToBatchListener();
|
||||||
|
|
||||||
// DND
|
// 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 { createSelector } from '@reduxjs/toolkit';
|
||||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
|
import IAIErrorLoadingImageFallback from 'common/components/IAIErrorLoadingImageFallback';
|
||||||
|
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||||
import {
|
import {
|
||||||
batchImageRangeEndSelected,
|
batchImageRangeEndSelected,
|
||||||
batchImageSelected,
|
batchImageSelected,
|
||||||
@ -32,11 +34,13 @@ type BatchImageProps = {
|
|||||||
|
|
||||||
const BatchImage = (props: BatchImageProps) => {
|
const BatchImage = (props: BatchImageProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { imageName } = props;
|
const { imageName } = props;
|
||||||
|
const {
|
||||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
currentData: imageDTO,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
isSuccess,
|
||||||
|
} = useGetImageDTOQuery(imageName);
|
||||||
const selector = useMemo(() => makeSelector(imageName), [imageName]);
|
const selector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||||
|
|
||||||
const { isSelected, selectionCount, selection } = useAppSelector(selector);
|
const { isSelected, selectionCount, selection } = useAppSelector(selector);
|
||||||
@ -76,8 +80,12 @@ const BatchImage = (props: BatchImageProps) => {
|
|||||||
}
|
}
|
||||||
}, [imageDTO, selection, selectionCount]);
|
}, [imageDTO, selection, selectionCount]);
|
||||||
|
|
||||||
if (!imageDTO) {
|
if (isLoading) {
|
||||||
return <Spinner />;
|
return <IAIFillSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError || !imageDTO) {
|
||||||
|
return <IAIErrorLoadingImageFallback />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -108,6 +116,7 @@ const BatchImage = (props: BatchImageProps) => {
|
|||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
resetTooltip="Remove from batch"
|
resetTooltip="Remove from batch"
|
||||||
withResetIcon
|
withResetIcon
|
||||||
|
thumbnail
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
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 { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
import { memo, useEffect, useRef, useState } from 'react';
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
@ -26,7 +26,6 @@ const selector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const BatchGrid = () => {
|
const BatchGrid = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const rootRef = useRef(null);
|
const rootRef = useRef(null);
|
||||||
const [scroller, setScroller] = useState<HTMLElement | null>(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 { createSelector } from '@reduxjs/toolkit';
|
||||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
@ -7,8 +7,12 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
|
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import { imageSelected } from '../store/gallerySlice';
|
import {
|
||||||
|
imageRangeEndSelected,
|
||||||
|
imageSelected,
|
||||||
|
imageSelectionToggled,
|
||||||
|
} from '../store/gallerySlice';
|
||||||
import ImageContextMenu from './ImageContextMenu';
|
import ImageContextMenu from './ImageContextMenu';
|
||||||
|
|
||||||
export const makeSelector = (image_name: string) =>
|
export const makeSelector = (image_name: string) =>
|
||||||
@ -23,33 +27,29 @@ export const makeSelector = (image_name: string) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
imageDTO: ImageDTO;
|
imageName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GalleryImage = (props: HoverableImageProps) => {
|
const GalleryImage = (props: HoverableImageProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { imageName } = props;
|
||||||
const { imageDTO } = props;
|
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||||
const { image_name } = imageDTO;
|
const localSelector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||||
|
|
||||||
const localSelector = useMemo(() => makeSelector(image_name), [image_name]);
|
|
||||||
|
|
||||||
const { isSelected, selectionCount, selection } =
|
const { isSelected, selectionCount, selection } =
|
||||||
useAppSelector(localSelector);
|
useAppSelector(localSelector);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
// multiselect disabled for now
|
if (e.shiftKey) {
|
||||||
// if (e.shiftKey) {
|
dispatch(imageRangeEndSelected(imageName));
|
||||||
// dispatch(imageRangeEndSelected(props.imageDTO.image_name));
|
} else if (e.ctrlKey || e.metaKey) {
|
||||||
// } else if (e.ctrlKey || e.metaKey) {
|
dispatch(imageSelectionToggled(imageName));
|
||||||
// dispatch(imageSelectionToggled(props.imageDTO.image_name));
|
} else {
|
||||||
// } else {
|
dispatch(imageSelected(imageName));
|
||||||
// dispatch(imageSelected(props.imageDTO.image_name));
|
}
|
||||||
// }
|
|
||||||
dispatch(imageSelected(props.imageDTO.image_name));
|
|
||||||
},
|
},
|
||||||
[dispatch, props.imageDTO.image_name]
|
[dispatch, imageName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
@ -81,13 +81,17 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
}
|
}
|
||||||
}, [imageDTO, selection, selectionCount]);
|
}, [imageDTO, selection, selectionCount]);
|
||||||
|
|
||||||
|
if (!imageDTO) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||||
<ImageContextMenu imageDTO={imageDTO}>
|
<ImageContextMenu imageDTO={imageDTO}>
|
||||||
{(ref) => (
|
{(ref) => (
|
||||||
<Box
|
<Box
|
||||||
position="relative"
|
position="relative"
|
||||||
key={image_name}
|
key={imageName}
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -2,7 +2,6 @@ import { ExternalLinkIcon } from '@chakra-ui/icons';
|
|||||||
import { MenuItem, MenuList } from '@chakra-ui/react';
|
import { MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { selectionAddedToBatch } from 'app/store/middleware/listenerMiddleware/listeners/selectionAddedToBatch';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
@ -10,6 +9,7 @@ import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
|||||||
import {
|
import {
|
||||||
imageAddedToBatch,
|
imageAddedToBatch,
|
||||||
imageRemovedFromBatch,
|
imageRemovedFromBatch,
|
||||||
|
imagesAddedToBatch,
|
||||||
} from 'features/batch/store/batchSlice';
|
} from 'features/batch/store/batchSlice';
|
||||||
import {
|
import {
|
||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
@ -158,7 +158,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
|||||||
}, [imageDTO.image_url]);
|
}, [imageDTO.image_url]);
|
||||||
|
|
||||||
const handleAddSelectionToBatch = useCallback(() => {
|
const handleAddSelectionToBatch = useCallback(() => {
|
||||||
dispatch(selectionAddedToBatch({ images_names: selection }));
|
dispatch(imagesAddedToBatch(selection));
|
||||||
}, [dispatch, selection]);
|
}, [dispatch, selection]);
|
||||||
|
|
||||||
const handleAddToBatch = useCallback(() => {
|
const handleAddToBatch = useCallback(() => {
|
||||||
|
@ -38,6 +38,7 @@ import {
|
|||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
import BatchGrid from './BatchGrid';
|
import BatchGrid from './BatchGrid';
|
||||||
|
import BoardGrid from './BoardGrid';
|
||||||
import BoardsList from './Boards/BoardsList';
|
import BoardsList from './Boards/BoardsList';
|
||||||
import ImageGalleryGrid from './ImageGalleryGrid';
|
import ImageGalleryGrid from './ImageGalleryGrid';
|
||||||
|
|
||||||
@ -228,7 +229,13 @@ const ImageGalleryContent = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex direction="column" gap={2} h="full" w="full">
|
<Flex direction="column" gap={2} h="full" w="full">
|
||||||
{selectedBoardId === 'batch' ? <BatchGrid /> : <ImageGalleryGrid />}
|
{selectedBoardId === 'batch' ? (
|
||||||
|
<BatchGrid />
|
||||||
|
) : selectedBoardId ? (
|
||||||
|
<BoardGrid board_id={selectedBoardId} />
|
||||||
|
) : (
|
||||||
|
<ImageGalleryGrid />
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
@ -160,7 +160,7 @@ const ImageGalleryGrid = () => {
|
|||||||
) : (
|
) : (
|
||||||
<GalleryImage
|
<GalleryImage
|
||||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||||
imageDTO={item}
|
imageName={item.image_name}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user