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

View File

@ -30,8 +30,9 @@ import {
FaTrash, FaTrash,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { import {
useChangeImagePinnedMutation,
useGetImageMetadataQuery, useGetImageMetadataQuery,
useStarImagesMutation,
useUnstarImagesMutation,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import { ImageDTO } from 'services/api/types'; import { ImageDTO } from 'services/api/types';
import { useDebounce } from 'use-debounce'; import { useDebounce } from 'use-debounce';
@ -63,7 +64,8 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
: debouncedMetadataQueryArg ?? skipToken : debouncedMetadataQueryArg ?? skipToken
); );
const [togglePin] = useChangeImagePinnedMutation(); const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const { isClipboardAPIAvailable, copyImageToClipboard } = const { isClipboardAPIAvailable, copyImageToClipboard } =
useCopyImageToClipboard(); useCopyImageToClipboard();
@ -133,13 +135,13 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
copyImageToClipboard(imageDTO.image_url); copyImageToClipboard(imageDTO.image_url);
}, [copyImageToClipboard, imageDTO.image_url]); }, [copyImageToClipboard, imageDTO.image_url]);
const handlePinImage = useCallback(() => { const handleStarImage = useCallback(() => {
togglePin({ imageDTO, pinned: true }); if (imageDTO) starImages({ images: [imageDTO] });
}, [togglePin, imageDTO]); }, [starImages, imageDTO]);
const handleUnpinImage = useCallback(() => { const handleUnstarImage = useCallback(() => {
togglePin({ imageDTO, pinned: false }); if (imageDTO) unstarImages({ images: [imageDTO] });
}, [togglePin, imageDTO]); }, [unstarImages, imageDTO]);
return ( return (
<> <>
@ -210,12 +212,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}> <MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
Change Board Change Board
</MenuItem> </MenuItem>
{imageDTO.pinned ? ( {imageDTO.starred ? (
<MenuItem icon={<MdStar />} onClickCapture={handleUnpinImage}> <MenuItem icon={<MdStar />} onClickCapture={handleUnstarImage}>
Unstar Image Unstar Image
</MenuItem> </MenuItem>
) : ( ) : (
<MenuItem icon={<MdStarBorder />} onClickCapture={handlePinImage}> <MenuItem icon={<MdStarBorder />} onClickCapture={handleStarImage}>
Star Image Star Image
</MenuItem> </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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIFillSkeleton from 'common/components/IAIFillSkeleton'; 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 { FaTrash } from 'react-icons/fa';
import { MdStar, MdStarBorder } from 'react-icons/md'; import { MdStar, MdStarBorder } from 'react-icons/md';
import { import {
useChangeImagePinnedMutation,
useGetImageDTOQuery, useGetImageDTOQuery,
useStarImagesMutation,
useUnstarImagesMutation,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon'; import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
@ -64,40 +65,26 @@ const GalleryImage = (props: HoverableImageProps) => {
} }
}, [imageDTO, selection, selectionCount]); }, [imageDTO, selection, selectionCount]);
const [togglePin] = useChangeImagePinnedMutation(); const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const togglePinnedState = useCallback(() => { const toggleStarredState = useCallback(() => {
if (imageDTO) { 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 [isHovered, setIsHovered] = useState(false);
const pinIcon = useMemo(() => { const starIcon = useMemo(() => {
if (imageDTO?.pinned) return <MdStar size="20" />; if (imageDTO?.starred) return <MdStar size="20" />;
if (!imageDTO?.pinned && isHovered) return <MdStarBorder size="20" />; if (!imageDTO?.starred && isHovered) return <MdStarBorder size="20" />;
}, [imageDTO?.pinned, isHovered]); }, [imageDTO?.starred, 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,
},
};
if (!imageDTO) { if (!imageDTO) {
return <IAIFillSkeleton />; return <IAIFillSkeleton />;
@ -130,9 +117,9 @@ const GalleryImage = (props: HoverableImageProps) => {
> >
<> <>
<IAIDndImageIcon <IAIDndImageIcon
onClick={togglePinnedState} onClick={toggleStarredState}
icon={pinIcon} icon={starIcon}
tooltip={imageDTO.pinned ? 'Unstar' : 'Star'} tooltip={imageDTO.starred ? 'Unstar' : 'Star'}
/> />
{isHovered && shouldShowDeleteButton && ( {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< starImages: build.mutation<
ImageDTO, void,
{ imageDTO: ImageDTO; pinned: boolean } { images: ImageDTO[] }
>({ >({
query: ({ imageDTO, pinned }) => ({ query: ({ images }) => ({
url: `images/i/${imageDTO.image_name}`, url: `images/star`,
method: 'PATCH', method: 'POST',
body: { pinned }, body: { image_names: images.map(img => img.image_name) },
}), }),
invalidatesTags: (result, error, { imageDTO }) => { invalidatesTags: (result, error, { images }) => {
const categories = getCategories(imageDTO); // assume all images are on the same board/category
return [ if (images[0]) {
{ const categories = getCategories(images[0]);
type: 'ImageList', const boardId = images[0].board_id;
id: getListImagesUrl({ return [
board_id: imageDTO.board_id, {
categories, type: 'ImageList',
}), id: getListImagesUrl({
}, board_id: boardId,
] categories,
}),
},
]
}
return []
}, },
async onQueryStarted( async onQueryStarted(
{ imageDTO, pinned }, { images },
{ dispatch, queryFulfilled, getState } { dispatch, queryFulfilled, getState }
) { ) {
/** try {
* Cache changes for `changeImagePinned`: /**
* - *update* getImageDTO * 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 // assume all images are on the same board/category
const patches: PatchCollection[] = []; if (!images[0]) return;
const categories = getCategories(images[0]);
const boardId = images[0].board_id;
// *update* getImageDTO images.forEach((imageDTO) => {
patches.push( const { image_name } = imageDTO;
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(
dispatch( dispatch(
imagesApi.util.updateQueryData( imagesApi.util.updateQueryData(
'listImages', 'getImageDTO',
queryArgs, image_name,
(draft) => { (draft) => {
imagesAdapter.upsertOne(draft, updatedImage); draft.starred = true;
} }
) )
) );
);
}
try { const queryArgs = {
await queryFulfilled; 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 { } 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, useRemoveImageFromBoardMutation,
useChangeImageIsIntermediateMutation, useChangeImageIsIntermediateMutation,
useChangeImageSessionIdMutation, useChangeImageSessionIdMutation,
useChangeImagePinnedMutation,
useDeleteBoardAndImagesMutation, useDeleteBoardAndImagesMutation,
useDeleteBoardMutation, useDeleteBoardMutation,
useStarImagesMutation,
useUnstarImagesMutation
} = imagesApi; } = imagesApi;

View File

@ -209,6 +209,14 @@ export type paths = {
/** Delete Images From List */ /** Delete Images From List */
post: operations['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/': { '/api/v1/boards/': {
/** /**
* List Boards * List Boards
@ -539,6 +547,22 @@ export type components = {
*/ */
image_names: string[]; 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 */
Body_upload_image: { Body_upload_image: {
/** /**
@ -1927,21 +1951,6 @@ export type components = {
nodes?: { nodes?: {
[key: string]: [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']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation'] | components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation'] | components['schemas']['MainModelLoaderInvocation']
@ -1949,21 +1958,15 @@ export type components = {
| components['schemas']['SDXLLoraLoaderInvocation'] | components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation'] | components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation'] | components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation'] | components['schemas']['RangeInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation'] | components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation'] | components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation'] | components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation'] | components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation'] | components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation'] | components['schemas']['LoadImageInvocation']
| 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']['ShowImageInvocation'] | components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation'] | components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation'] | components['schemas']['ImagePasteInvocation']
@ -1984,24 +1987,37 @@ export type components = {
| components['schemas']['ImageHueAdjustmentInvocation'] | components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation'] | components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation'] | components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation'] | components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation'] | components['schemas']['InfillColorInvocation']
| components['schemas']['StepParamEasingInvocation'] | 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']['AddInvocation']
| components['schemas']['SubtractInvocation'] | components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation'] | components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation'] | components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation'] | components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation'] | components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation'] | components['schemas']['ONNXPromptInvocation']
| components['schemas']['RangeOfSizeInvocation'] | components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['RandomRangeInvocation'] | 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']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation'] | components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation'] | components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation'] | components['schemas']['CollectInvocation']
@ -2507,10 +2523,10 @@ export type components = {
*/ */
node_id?: string; node_id?: string;
/** /**
* Pinned * Starred
* @description Whether this image is pinned. * @description Whether this image is starred.
*/ */
pinned: boolean; starred: boolean;
/** /**
* Board Id * Board Id
* @description The id of the board the image belongs to, if one exists. * @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 * - `image_category`: change the category of an image
* - `session_id`: change the session associated with an image * - `session_id`: change the session associated with an image
* - `is_intermediate`: change the image's `is_intermediate` flag * - `is_intermediate`: change the image's `is_intermediate` flag
* - `pinned`: change whether the image is pinned * - `starred`: change whether the image is starred
*/ */
ImageRecordChanges: { ImageRecordChanges: {
/** @description The image's new category. */ /** @description The image's new category. */
@ -2915,10 +2931,10 @@ export type components = {
*/ */
is_intermediate?: boolean; is_intermediate?: boolean;
/** /**
* Pinned * Starred
* @description The image's new `pinned` state * @description The image's new `starred` state
*/ */
pinned?: boolean; starred?: boolean;
}; };
/** /**
* Resize Image * Resize Image
@ -6390,36 +6406,20 @@ export type components = {
image?: components['schemas']['ImageField']; image?: components['schemas']['ImageField'];
}; };
/** /**
* UIConfigBase * StableDiffusion2ModelFormat
* @description Provides additional node configuration to the UI. * @description An enumeration.
* 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.
* @enum {string} * @enum {string}
*/ */
Input: 'connection' | 'direct' | 'any'; Input: 'connection' | 'direct' | 'any';
/** /**
* UIType * ControlNetModelFormat
* @description Type hints for the UI. * @description An enumeration.
* 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. * @enum {string}
*/
ControlNetModelFormat: 'checkpoint' | 'diffusers';
/**
* StableDiffusion1ModelFormat
* @description An enumeration.
* @enum {string} * @enum {string}
*/ */
UIType: UIType:
@ -6456,11 +6456,11 @@ export type components = {
| 'FilePath' | 'FilePath'
| 'enum'; | 'enum';
/** /**
* UIComponent * StableDiffusionOnnxModelFormat
* @description The type of UI component to use for a field, used to override the default components, which are inferred from the field type. * @description An enumeration.
* @enum {string} * @enum {string}
*/ */
UIComponent: 'none' | 'textarea' | 'slider'; StableDiffusionOnnxModelFormat: 'olive' | 'onnx';
/** /**
* _InputField * _InputField
* @description *DO NOT USE* * @description *DO NOT USE*
@ -6627,21 +6627,6 @@ export type operations = {
requestBody: { requestBody: {
content: { content: {
'application/json': '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']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation'] | components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation'] | components['schemas']['MainModelLoaderInvocation']
@ -6649,21 +6634,15 @@ export type operations = {
| components['schemas']['SDXLLoraLoaderInvocation'] | components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation'] | components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation'] | components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation'] | components['schemas']['RangeInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation'] | components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation'] | components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation'] | components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation'] | components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation'] | components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation'] | components['schemas']['LoadImageInvocation']
| 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']['ShowImageInvocation'] | components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation'] | components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation'] | components['schemas']['ImagePasteInvocation']
@ -6684,24 +6663,37 @@ export type operations = {
| components['schemas']['ImageHueAdjustmentInvocation'] | components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation'] | components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation'] | components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation'] | components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation'] | components['schemas']['InfillColorInvocation']
| components['schemas']['StepParamEasingInvocation'] | 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']['AddInvocation']
| components['schemas']['SubtractInvocation'] | components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation'] | components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation'] | components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation'] | components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation'] | components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation'] | components['schemas']['ONNXPromptInvocation']
| components['schemas']['RangeOfSizeInvocation'] | components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['RandomRangeInvocation'] | 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']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation'] | components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation'] | components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation'] | components['schemas']['CollectInvocation']
@ -6757,21 +6749,6 @@ export type operations = {
requestBody: { requestBody: {
content: { content: {
'application/json': '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']['ControlNetInvocation']
| components['schemas']['ImageProcessorInvocation'] | components['schemas']['ImageProcessorInvocation']
| components['schemas']['MainModelLoaderInvocation'] | components['schemas']['MainModelLoaderInvocation']
@ -6779,21 +6756,15 @@ export type operations = {
| components['schemas']['SDXLLoraLoaderInvocation'] | components['schemas']['SDXLLoraLoaderInvocation']
| components['schemas']['VaeLoaderInvocation'] | components['schemas']['VaeLoaderInvocation']
| components['schemas']['MetadataAccumulatorInvocation'] | components['schemas']['MetadataAccumulatorInvocation']
| components['schemas']['SDXLModelLoaderInvocation'] | components['schemas']['RangeInvocation']
| components['schemas']['SDXLRefinerModelLoaderInvocation'] | components['schemas']['RangeOfSizeInvocation']
| components['schemas']['RandomRangeInvocation']
| components['schemas']['ImageCollectionInvocation']
| components['schemas']['CompelInvocation'] | components['schemas']['CompelInvocation']
| components['schemas']['SDXLCompelPromptInvocation'] | components['schemas']['SDXLCompelPromptInvocation']
| components['schemas']['SDXLRefinerCompelPromptInvocation'] | components['schemas']['SDXLRefinerCompelPromptInvocation']
| components['schemas']['ClipSkipInvocation'] | components['schemas']['ClipSkipInvocation']
| components['schemas']['DenoiseLatentsInvocation'] | components['schemas']['LoadImageInvocation']
| 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']['ShowImageInvocation'] | components['schemas']['ShowImageInvocation']
| components['schemas']['ImageCropInvocation'] | components['schemas']['ImageCropInvocation']
| components['schemas']['ImagePasteInvocation'] | components['schemas']['ImagePasteInvocation']
@ -6814,24 +6785,37 @@ export type operations = {
| components['schemas']['ImageHueAdjustmentInvocation'] | components['schemas']['ImageHueAdjustmentInvocation']
| components['schemas']['ImageLuminosityAdjustmentInvocation'] | components['schemas']['ImageLuminosityAdjustmentInvocation']
| components['schemas']['ImageSaturationAdjustmentInvocation'] | components['schemas']['ImageSaturationAdjustmentInvocation']
| components['schemas']['DynamicPromptInvocation']
| components['schemas']['PromptsFromFileInvocation']
| components['schemas']['CvInpaintInvocation'] | components['schemas']['CvInpaintInvocation']
| components['schemas']['FloatLinearRangeInvocation'] | components['schemas']['InfillColorInvocation']
| components['schemas']['StepParamEasingInvocation'] | 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']['AddInvocation']
| components['schemas']['SubtractInvocation'] | components['schemas']['SubtractInvocation']
| components['schemas']['MultiplyInvocation'] | components['schemas']['MultiplyInvocation']
| components['schemas']['DivideInvocation'] | components['schemas']['DivideInvocation']
| components['schemas']['RandomIntInvocation'] | components['schemas']['RandomIntInvocation']
| components['schemas']['NoiseInvocation'] | components['schemas']['NoiseInvocation']
| components['schemas']['RangeInvocation'] | components['schemas']['ONNXPromptInvocation']
| components['schemas']['RangeOfSizeInvocation'] | components['schemas']['ONNXTextToLatentsInvocation']
| components['schemas']['RandomRangeInvocation'] | 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']['ESRGANInvocation']
| components['schemas']['InfillColorInvocation']
| components['schemas']['InfillTileInvocation']
| components['schemas']['InfillPatchMatchInvocation']
| components['schemas']['GraphInvocation'] | components['schemas']['GraphInvocation']
| components['schemas']['IterateInvocation'] | components['schemas']['IterateInvocation']
| components['schemas']['CollectInvocation'] | 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 * List Boards
* @description Gets a list of boards * @description Gets a list of boards

View File

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