mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
remove rest of cache, add bulk select UI
This commit is contained in:
parent
451c0f00e0
commit
62b4614aed
@ -385,6 +385,8 @@
|
|||||||
"viewerImage": "Viewer Image",
|
"viewerImage": "Viewer Image",
|
||||||
"compareImage": "Compare Image",
|
"compareImage": "Compare Image",
|
||||||
"openInViewer": "Open in Viewer",
|
"openInViewer": "Open in Viewer",
|
||||||
|
"selectAllOnPage": "Select All On Page",
|
||||||
|
"selectAllOnBoard": "Select All On Board",
|
||||||
"selectForCompare": "Select for Compare",
|
"selectForCompare": "Select for Compare",
|
||||||
"selectAnImageToCompare": "Select an Image to Compare",
|
"selectAnImageToCompare": "Select an Image to Compare",
|
||||||
"slider": "Slider",
|
"slider": "Slider",
|
||||||
|
@ -2,8 +2,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
|||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import type { ImageCache } from 'services/api/types';
|
import { getListImagesUrl } from 'services/api/util';
|
||||||
import { getListImagesUrl, imagesSelectors } from 'services/api/util';
|
|
||||||
|
|
||||||
export const addFirstListImagesListener = (startAppListening: AppStartListening) => {
|
export const addFirstListImagesListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -18,13 +17,10 @@ export const addFirstListImagesListener = (startAppListening: AppStartListening)
|
|||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
// TODO: figure out how to type the predicate
|
const data = action.payload;
|
||||||
const data = action.payload as ImageCache;
|
|
||||||
|
|
||||||
if (data.ids.length > 0) {
|
if (data.items.length > 0) {
|
||||||
// Select the first image
|
dispatch(imageSelected(data.items[0] ?? null));
|
||||||
const firstImage = imagesSelectors.selectAll(data)[0];
|
|
||||||
dispatch(imageSelected(firstImage ?? null));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
|||||||
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import { imagesSelectors } from 'services/api/util';
|
|
||||||
|
|
||||||
export const addBoardIdSelectedListener = (startAppListening: AppStartListening) => {
|
export const addBoardIdSelectedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -35,11 +34,12 @@ export const addBoardIdSelectedListener = (startAppListening: AppStartListening)
|
|||||||
const { data: boardImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
const { data: boardImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
||||||
|
|
||||||
if (boardImagesData && boardIdSelected.match(action) && action.payload.selectedImageName) {
|
if (boardImagesData && boardIdSelected.match(action) && action.payload.selectedImageName) {
|
||||||
const selectedImage = imagesSelectors.selectById(boardImagesData, action.payload.selectedImageName);
|
const selectedImage = boardImagesData.items.find(
|
||||||
|
(item) => item.image_name === action.payload.selectedImageName
|
||||||
|
);
|
||||||
dispatch(imageSelected(selectedImage || null));
|
dispatch(imageSelected(selectedImage || null));
|
||||||
} else if (boardImagesData) {
|
} else if (boardImagesData) {
|
||||||
const firstImage = imagesSelectors.selectAll(boardImagesData)[0];
|
dispatch(imageSelected(boardImagesData.items[0] || null));
|
||||||
dispatch(imageSelected(firstImage || null));
|
|
||||||
} else {
|
} else {
|
||||||
// board has no images - deselect
|
// board has no images - deselect
|
||||||
dispatch(imageSelected(null));
|
dispatch(imageSelected(null));
|
||||||
|
@ -22,11 +22,10 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
|
|||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
import { isImageFieldInputInstance } from 'features/nodes/types/field';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { clamp, forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { imagesSelectors } from 'services/api/util';
|
|
||||||
|
|
||||||
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.nodes.present.nodes.forEach((node) => {
|
state.nodes.present.nodes.forEach((node) => {
|
||||||
@ -123,23 +122,11 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
|||||||
const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
||||||
|
|
||||||
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
|
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
|
||||||
const { image_name } = imageDTO;
|
|
||||||
|
|
||||||
const baseQueryArgs = selectListImagesQueryArgs(state);
|
const baseQueryArgs = selectListImagesQueryArgs(state);
|
||||||
const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
||||||
|
|
||||||
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
|
if (data && data.items[0]) {
|
||||||
|
dispatch(imageSelected(data.items[0]));
|
||||||
const deletedImageIndex = cachedImageDTOs.findIndex((i) => i.image_name === image_name);
|
|
||||||
|
|
||||||
const filteredImageDTOs = cachedImageDTOs.filter((i) => i.image_name !== image_name);
|
|
||||||
|
|
||||||
const newSelectedImageIndex = clamp(deletedImageIndex, 0, filteredImageDTOs.length - 1);
|
|
||||||
|
|
||||||
const newSelectedImageDTO = filteredImageDTOs[newSelectedImageIndex];
|
|
||||||
|
|
||||||
if (newSelectedImageDTO) {
|
|
||||||
dispatch(imageSelected(newSelectedImageDTO));
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(imageSelected(null));
|
dispatch(imageSelected(null));
|
||||||
}
|
}
|
||||||
@ -188,10 +175,8 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
|
|||||||
const queryArgs = selectListImagesQueryArgs(state);
|
const queryArgs = selectListImagesQueryArgs(state);
|
||||||
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||||
|
|
||||||
const newSelectedImageDTO = data ? imagesSelectors.selectAll(data)[0] : undefined;
|
if (data && data.items[0]) {
|
||||||
|
dispatch(imageSelected(data.items[0]));
|
||||||
if (newSelectedImageDTO) {
|
|
||||||
dispatch(imageSelected(newSelectedImageDTO));
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(imageSelected(null));
|
dispatch(imageSelected(null));
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,12 @@ import {
|
|||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||||
import { imageSelected, imageToCompareChanged, isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import {
|
||||||
|
imageSelected,
|
||||||
|
imageToCompareChanged,
|
||||||
|
isImageViewerOpenChanged,
|
||||||
|
selectionChanged,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
@ -216,6 +221,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
board_id: boardId,
|
board_id: boardId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
dispatch(selectionChanged([]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +239,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
imageDTO,
|
imageDTO,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
dispatch(selectionChanged([]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +255,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
board_id: boardId,
|
board_id: boardId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
dispatch(selectionChanged([]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +269,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
imageDTOs,
|
imageDTOs,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
dispatch(selectionChanged([]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8,14 +8,14 @@ import {
|
|||||||
galleryViewChanged,
|
galleryViewChanged,
|
||||||
imageSelected,
|
imageSelected,
|
||||||
isImageViewerOpenChanged,
|
isImageViewerOpenChanged,
|
||||||
|
offsetChanged,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { IMAGE_CATEGORIES, IMAGE_LIMIT } from 'features/gallery/store/types';
|
|
||||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
import { CANVAS_OUTPUT } from 'features/nodes/util/graph/constants';
|
import { CANVAS_OUTPUT } from 'features/nodes/util/graph/constants';
|
||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import { imageListDefaultSort } from 'services/api/util';
|
import { getCategories, getListImagesUrl } from 'services/api/util';
|
||||||
import { socketInvocationComplete } from 'services/events/actions';
|
import { socketInvocationComplete } from 'services/events/actions';
|
||||||
|
|
||||||
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
|
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
|
||||||
@ -52,32 +52,6 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!imageDTO.is_intermediate) {
|
if (!imageDTO.is_intermediate) {
|
||||||
/**
|
|
||||||
* Cache updates for when an image result is received
|
|
||||||
* - add it to the no_board/images
|
|
||||||
*/
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imagesApi.util.updateQueryData(
|
|
||||||
'listImages',
|
|
||||||
{
|
|
||||||
board_id: imageDTO.board_id ?? 'none',
|
|
||||||
categories: IMAGE_CATEGORIES,
|
|
||||||
offset: gallery.offset,
|
|
||||||
limit: gallery.limit,
|
|
||||||
is_intermediate: false,
|
|
||||||
},
|
|
||||||
(draft) => {
|
|
||||||
const updatedListLength = draft.items.unshift(imageDTO);
|
|
||||||
draft.items.sort(imageListDefaultSort());
|
|
||||||
if (updatedListLength > IMAGE_LIMIT) {
|
|
||||||
draft.items.pop();
|
|
||||||
}
|
|
||||||
draft.total += 1;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// update the total images for the board
|
// update the total images for the board
|
||||||
dispatch(
|
dispatch(
|
||||||
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
||||||
@ -86,7 +60,18 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(imagesApi.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
|
dispatch(
|
||||||
|
imagesApi.util.invalidateTags([
|
||||||
|
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
||||||
|
{
|
||||||
|
type: 'ImageList',
|
||||||
|
id: getListImagesUrl({
|
||||||
|
board_id: imageDTO.board_id ?? 'none',
|
||||||
|
categories: getCategories(imageDTO),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
const { shouldAutoSwitch } = gallery;
|
const { shouldAutoSwitch } = gallery;
|
||||||
|
|
||||||
@ -106,6 +91,8 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(offsetChanged(0));
|
||||||
|
|
||||||
if (!imageDTO.board_id && gallery.selectedBoardId !== 'none') {
|
if (!imageDTO.board_id && gallery.selectedBoardId !== 'none') {
|
||||||
dispatch(
|
dispatch(
|
||||||
boardIdSelected({
|
boardIdSelected({
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
import { Flex, IconButton, Tag, TagCloseButton, TagLabel, Tooltip } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
|
||||||
|
import { selectionChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { BiSelectMultiple } from 'react-icons/bi';
|
||||||
|
|
||||||
|
export const GalleryBulkSelect = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { selection } = useAppSelector((s) => s.gallery);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { imageDTOs } = useGalleryImages();
|
||||||
|
|
||||||
|
const onClickClearSelection = useCallback(() => {
|
||||||
|
dispatch(selectionChanged([]));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const onClickSelectAllPage = useCallback(() => {
|
||||||
|
dispatch(selectionChanged(selection.concat(imageDTOs)));
|
||||||
|
}, [dispatch, imageDTOs, selection]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" justifyContent="space-between">
|
||||||
|
<Tag>
|
||||||
|
<TagLabel>
|
||||||
|
{selection.length} {t('common.selected')}
|
||||||
|
</TagLabel>
|
||||||
|
{selection.length > 0 && (
|
||||||
|
<Tooltip label="Clear selection">
|
||||||
|
<TagCloseButton onClick={onClickClearSelection} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
<Tooltip label={t('gallery.selectAllOnPage')}>
|
||||||
|
<IconButton
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
icon={<BiSelectMultiple />}
|
||||||
|
aria-label="Bulk select"
|
||||||
|
onClick={onClickSelectAllPage}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -10,6 +10,7 @@ import { RiServerLine } from 'react-icons/ri';
|
|||||||
|
|
||||||
import BoardsList from './Boards/BoardsList/BoardsList';
|
import BoardsList from './Boards/BoardsList/BoardsList';
|
||||||
import GalleryBoardName from './GalleryBoardName';
|
import GalleryBoardName from './GalleryBoardName';
|
||||||
|
import { GalleryBulkSelect } from './GalleryBulkSelect';
|
||||||
import GallerySettingsPopover from './GallerySettingsPopover';
|
import GallerySettingsPopover from './GallerySettingsPopover';
|
||||||
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
||||||
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
||||||
@ -71,6 +72,8 @@ const ImageGalleryContent = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<GalleryBulkSelect />
|
||||||
|
|
||||||
<GalleryImageGrid />
|
<GalleryImageGrid />
|
||||||
<GalleryPagination />
|
<GalleryPagination />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { offsetChanged } from 'features/gallery/store/gallerySlice';
|
import { offsetChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useListImagesQuery } from 'services/api/endpoints/images';
|
import { useListImagesQuery } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
||||||
@ -50,6 +50,13 @@ export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
|||||||
dispatch(offsetChanged((pages - 1) * (limit || 0)));
|
dispatch(offsetChanged((pages - 1) * (limit || 0)));
|
||||||
}, [dispatch, pages, limit]);
|
}, [dispatch, pages, limit]);
|
||||||
|
|
||||||
|
// handle when total/pages decrease and user is on high page number (ie bulk removing or deleting)
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPage + 1 > pages) {
|
||||||
|
goToLast();
|
||||||
|
}
|
||||||
|
}, [currentPage, pages, goToLast]);
|
||||||
|
|
||||||
// calculate the page buttons to display - current page with 3 around it
|
// calculate the page buttons to display - current page with 3 around it
|
||||||
const pageButtons = useMemo(() => {
|
const pageButtons = useMemo(() => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
@ -77,6 +84,10 @@ export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
|||||||
return `${startItem}-${endItem} of ${total}`;
|
return `${startItem}-${endItem} of ${total}`;
|
||||||
}, [total, currentPage, limit]);
|
}, [total, currentPage, limit]);
|
||||||
|
|
||||||
|
const numberOnPage = useMemo(() => {
|
||||||
|
return Math.min((currentPage + 1) * (limit || 0), total);
|
||||||
|
}, [currentPage, limit, total]);
|
||||||
|
|
||||||
const api = useMemo(
|
const api = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
count,
|
count,
|
||||||
@ -94,6 +105,7 @@ export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
|||||||
isFirstEnabled,
|
isFirstEnabled,
|
||||||
isLastEnabled,
|
isLastEnabled,
|
||||||
rangeDisplay,
|
rangeDisplay,
|
||||||
|
numberOnPage,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
count,
|
count,
|
||||||
@ -111,6 +123,7 @@ export const useGalleryPagination = (pageButtonsPerSide: number = 2) => {
|
|||||||
isFirstEnabled,
|
isFirstEnabled,
|
||||||
isLastEnabled,
|
isLastEnabled,
|
||||||
rangeDisplay,
|
rangeDisplay,
|
||||||
|
numberOnPage,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ export const gallerySlice = createSlice({
|
|||||||
offsetChanged: (state, action: PayloadAction<number>) => {
|
offsetChanged: (state, action: PayloadAction<number>) => {
|
||||||
state.offset = action.payload;
|
state.offset = action.payload;
|
||||||
},
|
},
|
||||||
limitChanged: (state, action: PayloadAction<number | undefined>) => {
|
limitChanged: (state, action: PayloadAction<number>) => {
|
||||||
state.limit = action.payload;
|
state.limit = action.payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,56 +1,9 @@
|
|||||||
import { createEntityAdapter } from '@reduxjs/toolkit';
|
|
||||||
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
|
|
||||||
import { dateComparator } from 'common/util/dateComparator';
|
import { dateComparator } from 'common/util/dateComparator';
|
||||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
import { buildV1Url } from 'services/api';
|
import { buildV1Url } from 'services/api';
|
||||||
|
|
||||||
import type { ImageCache, ImageDTO, ListImagesArgs } from './types';
|
import type { ImageDTO, ListImagesArgs } from './types';
|
||||||
|
|
||||||
export const getIsImageInDateRange = (data: ImageCache | undefined, imageDTO: ImageDTO) => {
|
|
||||||
if (!data) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalCachedImageDtos = imagesSelectors.selectAll(data);
|
|
||||||
|
|
||||||
if (totalCachedImageDtos.length <= 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cachedStarredImages = [];
|
|
||||||
const cachedUnstarredImages = [];
|
|
||||||
|
|
||||||
for (let index = 0; index < totalCachedImageDtos.length; index++) {
|
|
||||||
const image = totalCachedImageDtos[index];
|
|
||||||
if (image?.starred) {
|
|
||||||
cachedStarredImages.push(image);
|
|
||||||
}
|
|
||||||
if (!image?.starred) {
|
|
||||||
cachedUnstarredImages.push(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageDTO.starred) {
|
|
||||||
const lastStarredImage = cachedStarredImages[cachedStarredImages.length - 1];
|
|
||||||
// if starring or already starred, want to look in list of starred images
|
|
||||||
if (!lastStarredImage) {
|
|
||||||
return true;
|
|
||||||
} // no starred images showing, so always show this one
|
|
||||||
const createdDate = new Date(imageDTO.created_at);
|
|
||||||
const oldestDate = new Date(lastStarredImage.created_at);
|
|
||||||
return createdDate >= oldestDate;
|
|
||||||
} else {
|
|
||||||
const lastUnstarredImage = cachedUnstarredImages[cachedUnstarredImages.length - 1];
|
|
||||||
// if unstarring or already unstarred, want to look in list of unstarred images
|
|
||||||
if (!lastUnstarredImage) {
|
|
||||||
return false;
|
|
||||||
} // no unstarred images showing, so don't show this one
|
|
||||||
const createdDate = new Date(imageDTO.created_at);
|
|
||||||
const oldestDate = new Date(lastUnstarredImage.created_at);
|
|
||||||
return createdDate >= oldestDate;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCategories = (imageDTO: ImageDTO) => {
|
export const getCategories = (imageDTO: ImageDTO) => {
|
||||||
if (IMAGE_CATEGORIES.includes(imageDTO.image_category)) {
|
if (IMAGE_CATEGORIES.includes(imageDTO.image_category)) {
|
||||||
@ -59,22 +12,6 @@ export const getCategories = (imageDTO: ImageDTO) => {
|
|||||||
return ASSETS_CATEGORIES;
|
return ASSETS_CATEGORIES;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The adapter is not actually the data store - it just provides helper functions to interact
|
|
||||||
// with some other store of data. We will use the RTK Query cache as that store.
|
|
||||||
export const imagesAdapter = createEntityAdapter<ImageDTO, string>({
|
|
||||||
selectId: (image) => image.image_name,
|
|
||||||
sortComparer: (a, b) => {
|
|
||||||
// Compare starred images first
|
|
||||||
if (a.starred && !b.starred) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!a.starred && b.starred) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return dateComparator(b.created_at, a.created_at);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const imageListDefaultSort = () => {
|
export const imageListDefaultSort = () => {
|
||||||
return (a: ImageDTO, b: ImageDTO) => {
|
return (a: ImageDTO, b: ImageDTO) => {
|
||||||
if (a.starred && !b.starred) {
|
if (a.starred && !b.starred) {
|
||||||
@ -87,9 +24,6 @@ export const imageListDefaultSort = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create selectors for the adapter.
|
|
||||||
export const imagesSelectors = imagesAdapter.getSelectors(undefined, getSelectorsOptions);
|
|
||||||
|
|
||||||
// Helper to create the url for the listImages endpoint. Also we use it to create the cache key.
|
// Helper to create the url for the listImages endpoint. Also we use it to create the cache key.
|
||||||
export const getListImagesUrl = (queryArgs: ListImagesArgs) =>
|
export const getListImagesUrl = (queryArgs: ListImagesArgs) =>
|
||||||
buildV1Url(`images/?${queryString.stringify(queryArgs, { arrayFormat: 'none' })}`);
|
buildV1Url(`images/?${queryString.stringify(queryArgs, { arrayFormat: 'none' })}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user