rename pin to star, add multiselect and remove single image update api

This commit is contained in:
Mary Hipp 2023-08-15 12:55:28 -04:00 committed by psychedelicious
parent ee6a26a97d
commit 029a95550e
6 changed files with 456 additions and 300 deletions

View File

@ -5,13 +5,21 @@ import {
isModalOpenChanged,
} from 'features/changeBoardModal/store/slice';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { FaFolder, FaTrash } from 'react-icons/fa';
import { MdStar, MdStarBorder } from 'react-icons/md';
import {
useStarImagesMutation,
useUnstarImagesMutation,
} from '../../../../services/api/endpoints/images';
const MultipleSelectionMenuItems = () => {
const dispatch = useAppDispatch();
const selection = useAppSelector((state) => state.gallery.selection);
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const handleChangeBoard = useCallback(() => {
dispatch(imagesToChangeSelected(selection));
dispatch(isModalOpenChanged(true));
@ -21,8 +29,37 @@ const MultipleSelectionMenuItems = () => {
dispatch(imagesToDeleteSelected(selection));
}, [dispatch, selection]);
const handleStarSelection = useCallback(() => {
starImages({ images: selection });
}, [starImages, selection]);
const handleUnstarSelection = useCallback(() => {
unstarImages({ images: selection });
}, [unstarImages, selection]);
const areAllStarred = useMemo(() => {
return selection.every((img) => img.starred);
}, [selection]);
const areAllUnstarred = useMemo(() => {
return selection.every((img) => !img.starred);
}, [selection]);
return (
<>
{areAllStarred && (
<MenuItem
icon={<MdStarBorder />}
onClickCapture={handleUnstarSelection}
>
Unstar All
</MenuItem>
)}
{areAllUnstarred && (
<MenuItem icon={<MdStar />} onClickCapture={handleStarSelection}>
Star All
</MenuItem>
)}
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
Change Board
</MenuItem>

View File

@ -30,8 +30,9 @@ import {
FaTrash,
} from 'react-icons/fa';
import {
useChangeImagePinnedMutation,
useGetImageMetadataQuery,
useStarImagesMutation,
useUnstarImagesMutation,
} from 'services/api/endpoints/images';
import { ImageDTO } from 'services/api/types';
import { useDebounce } from 'use-debounce';
@ -63,7 +64,8 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
: debouncedMetadataQueryArg ?? skipToken
);
const [togglePin] = useChangeImagePinnedMutation();
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const { isClipboardAPIAvailable, copyImageToClipboard } =
useCopyImageToClipboard();
@ -133,13 +135,13 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
copyImageToClipboard(imageDTO.image_url);
}, [copyImageToClipboard, imageDTO.image_url]);
const handlePinImage = useCallback(() => {
togglePin({ imageDTO, pinned: true });
}, [togglePin, imageDTO]);
const handleStarImage = useCallback(() => {
if (imageDTO) starImages({ images: [imageDTO] });
}, [starImages, imageDTO]);
const handleUnpinImage = useCallback(() => {
togglePin({ imageDTO, pinned: false });
}, [togglePin, imageDTO]);
const handleUnstarImage = useCallback(() => {
if (imageDTO) unstarImages({ images: [imageDTO] });
}, [unstarImages, imageDTO]);
return (
<>
@ -210,12 +212,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
Change Board
</MenuItem>
{imageDTO.pinned ? (
<MenuItem icon={<MdStar />} onClickCapture={handleUnpinImage}>
{imageDTO.starred ? (
<MenuItem icon={<MdStar />} onClickCapture={handleUnstarImage}>
Unstar Image
</MenuItem>
) : (
<MenuItem icon={<MdStarBorder />} onClickCapture={handlePinImage}>
<MenuItem icon={<MdStarBorder />} onClickCapture={handleStarImage}>
Star Image
</MenuItem>
)}

View File

@ -1,4 +1,4 @@
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
@ -13,8 +13,9 @@ import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
import { FaTrash } from 'react-icons/fa';
import { MdStar, MdStarBorder } from 'react-icons/md';
import {
useChangeImagePinnedMutation,
useGetImageDTOQuery,
useStarImagesMutation,
useUnstarImagesMutation,
} from 'services/api/endpoints/images';
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
@ -64,40 +65,26 @@ const GalleryImage = (props: HoverableImageProps) => {
}
}, [imageDTO, selection, selectionCount]);
const [togglePin] = useChangeImagePinnedMutation();
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const togglePinnedState = useCallback(() => {
const toggleStarredState = useCallback(() => {
if (imageDTO) {
togglePin({ imageDTO, pinned: !imageDTO.pinned });
if (imageDTO.starred) {
unstarImages({ images: [imageDTO] });
}
if (!imageDTO.starred) {
starImages({ images: [imageDTO] });
}
}
}, [togglePin, imageDTO]);
}, [starImages, unstarImages, imageDTO]);
const [isHovered, setIsHovered] = useState(false);
const pinIcon = useMemo(() => {
if (imageDTO?.pinned) return <MdStar size="20" />;
if (!imageDTO?.pinned && isHovered) return <MdStarBorder size="20" />;
}, [imageDTO?.pinned, isHovered]);
const resetIconShadow = useColorModeValue(
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
);
const iconButtonStyles = {
position: 'absolute',
top: 1,
insetInlineEnd: 1,
p: 0,
minW: 0,
svg: {
transitionProperty: 'common',
transitionDuration: 'normal',
fill: 'base.100',
_hover: { fill: 'base.50' },
filter: resetIconShadow,
},
};
const starIcon = useMemo(() => {
if (imageDTO?.starred) return <MdStar size="20" />;
if (!imageDTO?.starred && isHovered) return <MdStarBorder size="20" />;
}, [imageDTO?.starred, isHovered]);
if (!imageDTO) {
return <IAIFillSkeleton />;
@ -130,9 +117,9 @@ const GalleryImage = (props: HoverableImageProps) => {
>
<>
<IAIDndImageIcon
onClick={togglePinnedState}
icon={pinIcon}
tooltip={imageDTO.pinned ? 'Unstar' : 'Star'}
onClick={toggleStarredState}
icon={starIcon}
tooltip={imageDTO.starred ? 'Unstar' : 'Star'}
/>
{isHovered && shouldShowDeleteButton && (

View File

@ -388,112 +388,217 @@ export const imagesApi = api.injectEndpoints({
},
}),
/**
* Change an image's `pinned` state.
* Star a list of images.
*/
changeImagePinned: build.mutation<
ImageDTO,
{ imageDTO: ImageDTO; pinned: boolean }
starImages: build.mutation<
void,
{ images: ImageDTO[] }
>({
query: ({ imageDTO, pinned }) => ({
url: `images/i/${imageDTO.image_name}`,
method: 'PATCH',
body: { pinned },
query: ({ images }) => ({
url: `images/star`,
method: 'POST',
body: { image_names: images.map(img => img.image_name) },
}),
invalidatesTags: (result, error, { imageDTO }) => {
const categories = getCategories(imageDTO);
return [
{
type: 'ImageList',
id: getListImagesUrl({
board_id: imageDTO.board_id,
categories,
}),
},
]
invalidatesTags: (result, error, { images }) => {
// assume all images are on the same board/category
if (images[0]) {
const categories = getCategories(images[0]);
const boardId = images[0].board_id;
return [
{
type: 'ImageList',
id: getListImagesUrl({
board_id: boardId,
categories,
}),
},
]
}
return []
},
async onQueryStarted(
{ imageDTO, pinned },
{ images },
{ dispatch, queryFulfilled, getState }
) {
/**
* Cache changes for `changeImagePinned`:
* - *update* getImageDTO
*/
try {
/**
* Cache changes for pinImages:
* - *update* getImageDTO for each image
* - *upsert* into list for each image
*/
// Store patches so we can undo if the query fails
const patches: PatchCollection[] = [];
// assume all images are on the same board/category
if (!images[0]) return;
const categories = getCategories(images[0]);
const boardId = images[0].board_id;
// *update* getImageDTO
patches.push(
dispatch(
imagesApi.util.updateQueryData(
'getImageDTO',
imageDTO.image_name,
(draft) => {
Object.assign(draft, { pinned });
}
)
)
);
const categories = getCategories(imageDTO);
const queryArgs = {
board_id: imageDTO.board_id ?? 'none',
categories,
};
const currentCache = imagesApi.endpoints.listImages.select(queryArgs)(
getState()
);
const { data: total } = IMAGE_CATEGORIES.includes(
imageDTO.image_category
)
? boardsApi.endpoints.getBoardImagesTotal.select(
imageDTO.board_id ?? 'none'
)(getState())
: boardsApi.endpoints.getBoardAssetsTotal.select(
imageDTO.board_id ?? 'none'
)(getState());
// IF it eligible for insertion into existing $cache
// "eligible" means either:
// - The cache is fully populated, with all images in the db cached
// OR
// - The image's `created_at` is within the range of the cached images within that pinned state
const updatedImage: ImageDTO = { ...imageDTO, pinned }
const isCacheFullyPopulated =
currentCache.data && currentCache.data.ids.length >= (total ?? 0);
const isInDateRange = getIsImageInDateRange(
currentCache.data,
updatedImage
);
// should we remove images from cache if _not_ in date range? ie you are showing 100 of 101 pinned images and you unpin one. technically it should disappear from list.
if (isCacheFullyPopulated || isInDateRange) {
// *upsert* to $cache
patches.push(
images.forEach((imageDTO) => {
const { image_name } = imageDTO;
dispatch(
imagesApi.util.updateQueryData(
'listImages',
queryArgs,
'getImageDTO',
image_name,
(draft) => {
imagesAdapter.upsertOne(draft, updatedImage);
draft.starred = true;
}
)
)
);
}
);
try {
await queryFulfilled;
const queryArgs = {
board_id: boardId ?? 'none',
categories
};
const currentCache = imagesApi.endpoints.listImages.select(
queryArgs
)(getState());
const { data: previousTotal } = IMAGE_CATEGORIES.includes(
imageDTO.image_category
)
? boardsApi.endpoints.getBoardImagesTotal.select(
boardId ?? 'none'
)(getState())
: boardsApi.endpoints.getBoardAssetsTotal.select(
boardId ?? 'none'
)(getState());
const isCacheFullyPopulated =
currentCache.data &&
currentCache.data.ids.length >= (previousTotal ?? 0);
const isInDateRange =
(previousTotal || 0) >= IMAGE_LIMIT
? getIsImageInDateRange(currentCache.data, imageDTO)
: true;
if (isCacheFullyPopulated || isInDateRange) {
// *upsert* to $cache
dispatch(
imagesApi.util.updateQueryData(
'listImages',
queryArgs,
(draft) => {
imagesAdapter.upsertOne(draft, {
...imageDTO,
starred: true
});
}
)
);
}
});
} catch {
patches.forEach((patchResult) => patchResult.undo());
// no-op
}
},
}),
/**
* Unstar a list of images.
*/
unstarImages: build.mutation<
void,
{ images: ImageDTO[] }
>({
query: ({ images }) => ({
url: `images/unstar`,
method: 'POST',
body: { image_names: images.map(img => img.image_name) },
}),
invalidatesTags: (result, error, { images }) => {
// assume all images are on the same board/category
if (images[0]) {
const categories = getCategories(images[0]);
const boardId = images[0].board_id;
return [
{
type: 'ImageList',
id: getListImagesUrl({
board_id: boardId,
categories,
}),
},
]
}
return []
},
async onQueryStarted(
{ images },
{ dispatch, queryFulfilled, getState }
) {
try {
/**
* Cache changes for unstarImages:
* - *update* getImageDTO for each image
* - *upsert* into list for each image
*/
// assume all images are on the same board/category
if (!images[0]) return;
const categories = getCategories(images[0]);
const boardId = images[0].board_id;
images.forEach((imageDTO) => {
const { image_name } = imageDTO;
dispatch(
imagesApi.util.updateQueryData(
'getImageDTO',
image_name,
(draft) => {
draft.starred = false;
}
)
);
const queryArgs = {
board_id: boardId ?? 'none',
categories
};
const currentCache = imagesApi.endpoints.listImages.select(
queryArgs
)(getState());
const { data: previousTotal } = IMAGE_CATEGORIES.includes(
imageDTO.image_category
)
? boardsApi.endpoints.getBoardImagesTotal.select(
boardId ?? 'none'
)(getState())
: boardsApi.endpoints.getBoardAssetsTotal.select(
boardId ?? 'none'
)(getState());
const isCacheFullyPopulated =
currentCache.data &&
currentCache.data.ids.length >= (previousTotal ?? 0);
const isInDateRange =
(previousTotal || 0) >= IMAGE_LIMIT
? getIsImageInDateRange(currentCache.data, imageDTO)
: true;
if (isCacheFullyPopulated || isInDateRange) {
// *upsert* to $cache
dispatch(
imagesApi.util.updateQueryData(
'listImages',
queryArgs,
(draft) => {
imagesAdapter.upsertOne(draft, {
...imageDTO,
starred: false
});
}
)
);
}
});
} catch {
// no-op
}
},
}),
@ -1268,7 +1373,8 @@ export const {
useRemoveImageFromBoardMutation,
useChangeImageIsIntermediateMutation,
useChangeImageSessionIdMutation,
useChangeImagePinnedMutation,
useDeleteBoardAndImagesMutation,
useDeleteBoardMutation,
useStarImagesMutation,
useUnstarImagesMutation
} = imagesApi;

View File

@ -209,6 +209,14 @@ export type paths = {
/** Delete Images From List */
post: operations['delete_images_from_list'];
};
'/api/v1/images/star': {
/** Star Images In List */
post: operations['star_images_in_list'];
};
'/api/v1/images/unstar': {
/** Unstar Images In List */
post: operations['unstar_images_in_list'];
};
'/api/v1/boards/': {
/**
* List Boards
@ -539,6 +547,22 @@ export type components = {
*/
image_names: string[];
};
/** Body_star_images_in_list */
Body_star_images_in_list: {
/**
* Image Names
* @description The list of names of images to star
*/
image_names: string[];
};
/** Body_unstar_images_in_list */
Body_unstar_images_in_list: {
/**
* Image Names
* @description The list of names of images to unstar
*/
image_names: string[];
};
/** Body_upload_image */
Body_upload_image: {
/**
@ -1927,21 +1951,6 @@ export type components = {
nodes?: {
[key: string]:
| (
| components['schemas']['BooleanInvocation']
| components['schemas']['BooleanCollectionInvocation']
| components['schemas']['IntegerInvocation']
| components['schemas']['IntegerCollectionInvocation']
| components['schemas']['FloatInvocation']
| components['schemas']['FloatCollectionInvocation']
| components['schemas']['StringInvocation']
| components['schemas']['StringCollectionInvocation']
| components['schemas']['ImageInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['LatentsInvocation']
| components['schemas']['LatentsCollectionInvocation']
| components['schemas']['ColorInvocation']
| components['schemas']['ConditioningInvocation']
| components['schemas']['ConditioningCollectionInvocation']
| components['schemas']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation']
@ -1949,21 +1958,15 @@ export type components = {
| components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['LoadImageInvocation']
| components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation']
@ -1984,24 +1987,37 @@ export type components = {
| components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['AddInvocation']
| components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['ONNXSD1ModelLoaderInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['ParamIntInvocation']
| components['schemas']['ParamFloatInvocation']
| components['schemas']['ParamStringInvocation']
| components['schemas']['ParamPromptInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation']
@ -2507,10 +2523,10 @@ export type components = {
*/
node_id?: string;
/**
* Pinned
* @description Whether this image is pinned.
* Starred
* @description Whether this image is starred.
*/
pinned: boolean;
starred: boolean;
/**
* Board Id
* @description The id of the board the image belongs to, if one exists.
@ -2899,7 +2915,7 @@ export type components = {
* - `image_category`: change the category of an image
* - `session_id`: change the session associated with an image
* - `is_intermediate`: change the image's `is_intermediate` flag
* - `pinned`: change whether the image is pinned
* - `starred`: change whether the image is starred
*/
ImageRecordChanges: {
/** @description The image's new category. */
@ -2915,10 +2931,10 @@ export type components = {
*/
is_intermediate?: boolean;
/**
* Pinned
* @description The image's new `pinned` state
* Starred
* @description The image's new `starred` state
*/
pinned?: boolean;
starred?: boolean;
};
/**
* Resize Image
@ -6390,36 +6406,20 @@ export type components = {
image?: components['schemas']['ImageField'];
};
/**
* UIConfigBase
* @description Provides additional node configuration to the UI.
* This is used internally by the @tags and @title decorator logic. You probably want to use those
* decorators, though you may add this class to a node definition to specify the title and tags.
*/
UIConfigBase: {
/**
* Tags
* @description The tags to display in the UI
*/
tags?: string[];
/**
* Title
* @description The display name of the node
*/
title?: string;
};
/**
* Input
* @description The type of input a field accepts.
* - `Input.Direct`: The field must have its value provided directly, when the invocation and field are instantiated.
* - `Input.Connection`: The field must have its value provided by a connection.
* - `Input.Any`: The field may have its value provided either directly or by a connection.
* StableDiffusion2ModelFormat
* @description An enumeration.
* @enum {string}
*/
Input: 'connection' | 'direct' | 'any';
/**
* UIType
* @description Type hints for the UI.
* If a field should be provided a data type that does not exactly match the python type of the field, use this to provide the type that should be used instead. See the node development docs for detail on adding a new field type, which involves client-side changes.
* ControlNetModelFormat
* @description An enumeration.
* @enum {string}
*/
ControlNetModelFormat: 'checkpoint' | 'diffusers';
/**
* StableDiffusion1ModelFormat
* @description An enumeration.
* @enum {string}
*/
UIType:
@ -6456,11 +6456,11 @@ export type components = {
| 'FilePath'
| 'enum';
/**
* UIComponent
* @description The type of UI component to use for a field, used to override the default components, which are inferred from the field type.
* StableDiffusionOnnxModelFormat
* @description An enumeration.
* @enum {string}
*/
UIComponent: 'none' | 'textarea' | 'slider';
StableDiffusionOnnxModelFormat: 'olive' | 'onnx';
/**
* _InputField
* @description *DO NOT USE*
@ -6627,21 +6627,6 @@ export type operations = {
requestBody: {
content: {
'application/json':
| components['schemas']['BooleanInvocation']
| components['schemas']['BooleanCollectionInvocation']
| components['schemas']['IntegerInvocation']
| components['schemas']['IntegerCollectionInvocation']
| components['schemas']['FloatInvocation']
| components['schemas']['FloatCollectionInvocation']
| components['schemas']['StringInvocation']
| components['schemas']['StringCollectionInvocation']
| components['schemas']['ImageInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['LatentsInvocation']
| components['schemas']['LatentsCollectionInvocation']
| components['schemas']['ColorInvocation']
| components['schemas']['ConditioningInvocation']
| components['schemas']['ConditioningCollectionInvocation']
| components['schemas']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation']
@ -6649,21 +6634,15 @@ export type operations = {
| components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['LoadImageInvocation']
| components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation']
@ -6684,24 +6663,37 @@ export type operations = {
| components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['AddInvocation']
| components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['ONNXSD1ModelLoaderInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['ParamIntInvocation']
| components['schemas']['ParamFloatInvocation']
| components['schemas']['ParamStringInvocation']
| components['schemas']['ParamPromptInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation']
@ -6757,21 +6749,6 @@ export type operations = {
requestBody: {
content: {
'application/json':
| components['schemas']['BooleanInvocation']
| components['schemas']['BooleanCollectionInvocation']
| components['schemas']['IntegerInvocation']
| components['schemas']['IntegerCollectionInvocation']
| components['schemas']['FloatInvocation']
| components['schemas']['FloatCollectionInvocation']
| components['schemas']['StringInvocation']
| components['schemas']['StringCollectionInvocation']
| components['schemas']['ImageInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['LatentsInvocation']
| components['schemas']['LatentsCollectionInvocation']
| components['schemas']['ColorInvocation']
| components['schemas']['ConditioningInvocation']
| components['schemas']['ConditioningCollectionInvocation']
| components['schemas']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation']
@ -6779,21 +6756,15 @@ export type operations = {
| components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['LoadImageInvocation']
| components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation']
@ -6814,24 +6785,37 @@ export type operations = {
| components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['DenoiseLatentsInvocation']
| components['schemas']['LatentsToImageInvocation']
| components['schemas']['ResizeLatentsInvocation']
| components['schemas']['ScaleLatentsInvocation']
| components['schemas']['ImageToLatentsInvocation']
| components['schemas']['AddInvocation']
| components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation']
| components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ONNXPromptInvocation']
| components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['ONNXLatentsToImageInvocation']
| components['schemas']['ONNXSD1ModelLoaderInvocation']
| components['schemas']['OnnxModelLoaderInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['ParamIntInvocation']
| components['schemas']['ParamFloatInvocation']
| components['schemas']['ParamStringInvocation']
| components['schemas']['ParamPromptInvocation']
| components['schemas']['FloatLinearRangeInvocation']
| components['schemas']['StepParamEasingInvocation']
| components['schemas']['SDXLModelLoaderInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation']
| components['schemas']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation']
@ -7723,6 +7707,50 @@ export type operations = {
};
};
};
/** Star Images In List */
star_images_in_list: {
requestBody: {
content: {
'application/json': components['schemas']['Body_star_images_in_list'];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
'application/json': unknown;
};
};
/** @description Validation Error */
422: {
content: {
'application/json': components['schemas']['HTTPValidationError'];
};
};
};
};
/** Unstar Images In List */
unstar_images_in_list: {
requestBody: {
content: {
'application/json': components['schemas']['Body_unstar_images_in_list'];
};
};
responses: {
/** @description Successful Response */
200: {
content: {
'application/json': unknown;
};
};
/** @description Validation Error */
422: {
content: {
'application/json': components['schemas']['HTTPValidationError'];
};
};
};
};
/**
* List Boards
* @description Gets a list of boards

View File

@ -21,32 +21,28 @@ export const getIsImageInDateRange = (
return true;
}
const cachedPinnedImages = [];
const cachedUnpinnedImages = [];
const cachedStarredImages = [];
const cachedUnstarredImages = [];
for (let index = 0; index < totalCachedImageDtos.length; index++) {
const image = totalCachedImageDtos[index];
if (image?.pinned) cachedPinnedImages.push(image)
if (!image?.pinned) cachedUnpinnedImages.push(image)
if (image?.starred) cachedStarredImages.push(image)
if (!image?.starred) cachedUnstarredImages.push(image)
}
const lastPinnedImage = cachedPinnedImages[cachedPinnedImages.length - 1];
const lastUnpinnedImage = cachedUnpinnedImages[cachedUnpinnedImages.length - 1];
if (!lastPinnedImage || !lastUnpinnedImage) {
// satisfy TS gods, we already confirmed the array has more than one image
return false;
}
if (imageDTO.pinned) {
// if pinning or already pinned, want to look in list of pinned images
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(lastPinnedImage.created_at);
const oldestDate = new Date(lastStarredImage.created_at);
return createdDate >= oldestDate;
} else {
// if unpinning or already unpinned, want to look in list of unpinned images
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(lastUnpinnedImage.created_at);
const oldestDate = new Date(lastUnstarredImage.created_at);
return createdDate >= oldestDate;
}
@ -64,11 +60,11 @@ export const getCategories = (imageDTO: ImageDTO) => {
export const imagesAdapter = createEntityAdapter<ImageDTO>({
selectId: (image) => image.image_name,
sortComparer: (a, b) => {
// Compare pinned images first
if (a.pinned && !b.pinned) {
// Compare starred images first
if (a.starred && !b.starred) {
return -1;
}
if (!a.pinned && b.pinned) {
if (!a.starred && b.starred) {
return 1;
}
return dateComparator(b.created_at, a.created_at)