This commit is contained in:
psychedelicious 2023-07-10 23:33:21 +10:00
parent 58cb5fefd0
commit 2990fa23fe
10 changed files with 227 additions and 35 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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>
)}

View File

@ -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);

View File

@ -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);

View File

@ -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={{

View File

@ -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(() => {

View File

@ -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>
);

View File

@ -160,7 +160,7 @@ const ImageGalleryGrid = () => {
) : (
<GalleryImage
key={`${item.image_name}-${item.thumbnail_url}`}
imageDTO={item}
imageName={item.image_name}
/>
)
}