mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): wip use new images service
This commit is contained in:
committed by
Kent Keirsey
parent
74292eba28
commit
6aebe1614d
@@ -61,8 +61,8 @@ const CurrentImagePreview = () => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.image_type);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
@@ -108,7 +108,7 @@ const CurrentImagePreview = () => {
|
||||
image && (
|
||||
<>
|
||||
<Image
|
||||
src={getUrl(image.url)}
|
||||
src={getUrl(image.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onDragStart={handleDragStart}
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
sentImageToImg2Img,
|
||||
} from '../store/actions';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
export const selector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
@@ -70,14 +71,16 @@ export const selector = createSelector(
|
||||
);
|
||||
|
||||
interface HoverableImageProps {
|
||||
image: InvokeAI.Image;
|
||||
image: ImageDTO;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
const memoEqualityCheck = (
|
||||
prev: HoverableImageProps,
|
||||
next: HoverableImageProps
|
||||
) => prev.image.name === next.image.name && prev.isSelected === next.isSelected;
|
||||
) =>
|
||||
prev.image.image_name === next.image.image_name &&
|
||||
prev.isSelected === next.isSelected;
|
||||
|
||||
/**
|
||||
* Gallery image component with delete/use all/use seed buttons on hover.
|
||||
@@ -100,7 +103,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
} = useDisclosure();
|
||||
|
||||
const { image, isSelected } = props;
|
||||
const { url, thumbnail, name } = image;
|
||||
const { image_url, thumbnail_url, image_name } = image;
|
||||
const { getUrl } = useGetUrl();
|
||||
|
||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||
@@ -144,8 +147,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.image_type);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
@@ -153,11 +156,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallPrompt(image.metadata?.invokeai?.node?.prompt);
|
||||
recallPrompt(image.metadata?.positive_conditioning);
|
||||
}, [image, recallPrompt]);
|
||||
|
||||
const handleRecallSeed = useCallback(() => {
|
||||
recallSeed(image.metadata.invokeai?.node?.seed);
|
||||
recallSeed(image.metadata?.seed);
|
||||
}, [image, recallSeed]);
|
||||
|
||||
const handleSendToImageToImage = useCallback(() => {
|
||||
@@ -200,7 +203,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
};
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
window.open(getUrl(image.url), '_blank');
|
||||
window.open(getUrl(image.image_url), '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -223,7 +226,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallPrompt}
|
||||
isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined}
|
||||
isDisabled={image?.metadata?.positive_conditioning === undefined}
|
||||
>
|
||||
{t('parameters.usePrompt')}
|
||||
</MenuItem>
|
||||
@@ -231,14 +234,14 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallSeed}
|
||||
isDisabled={image?.metadata?.invokeai?.node?.seed === undefined}
|
||||
isDisabled={image?.metadata?.seed === undefined}
|
||||
>
|
||||
{t('parameters.useSeed')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallInitialImage}
|
||||
isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'}
|
||||
isDisabled={image?.metadata?.type !== 'img2img'}
|
||||
>
|
||||
{t('parameters.useInitImg')}
|
||||
</MenuItem>
|
||||
@@ -247,7 +250,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
onClickCapture={handleUseAllParameters}
|
||||
isDisabled={
|
||||
!['txt2img', 'img2img', 'inpaint'].includes(
|
||||
String(image?.metadata?.invokeai?.node?.type)
|
||||
String(image?.metadata?.type)
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -278,7 +281,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
{(ref) => (
|
||||
<Box
|
||||
position="relative"
|
||||
key={name}
|
||||
key={image_name}
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
userSelect="none"
|
||||
@@ -303,7 +306,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
||||
}
|
||||
rounded="md"
|
||||
src={getUrl(thumbnail || url)}
|
||||
src={getUrl(thumbnail_url || image_url)}
|
||||
fallback={<FaImage />}
|
||||
sx={{
|
||||
width: '100%',
|
||||
|
||||
@@ -55,6 +55,7 @@ import { Image as ImageType } from 'app/types/invokeai';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import GalleryProgressImage from './GalleryProgressImage';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
|
||||
const PROGRESS_IMAGE_PLACEHOLDER = 'PROGRESS_IMAGE_PLACEHOLDER';
|
||||
@@ -66,7 +67,7 @@ const categorySelector = createSelector(
|
||||
const { currentCategory } = gallery;
|
||||
|
||||
if (currentCategory === 'results') {
|
||||
const tempImages: (ImageType | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = [];
|
||||
const tempImages: (ImageDTO | typeof PROGRESS_IMAGE_PLACEHOLDER)[] = [];
|
||||
|
||||
if (system.progressImage) {
|
||||
tempImages.push(PROGRESS_IMAGE_PLACEHOLDER);
|
||||
@@ -352,7 +353,7 @@ const ImageGalleryContent = () => {
|
||||
const isSelected =
|
||||
image === PROGRESS_IMAGE_PLACEHOLDER
|
||||
? false
|
||||
: selectedImage?.name === image?.name;
|
||||
: selectedImage?.image_name === image?.image_name;
|
||||
|
||||
return (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
@@ -362,7 +363,7 @@ const ImageGalleryContent = () => {
|
||||
/>
|
||||
) : (
|
||||
<HoverableImage
|
||||
key={`${image.name}-${image.thumbnail}`}
|
||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
||||
image={image}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
@@ -385,13 +386,13 @@ const ImageGalleryContent = () => {
|
||||
const isSelected =
|
||||
image === PROGRESS_IMAGE_PLACEHOLDER
|
||||
? false
|
||||
: selectedImage?.name === image?.name;
|
||||
: selectedImage?.image_name === image?.image_name;
|
||||
|
||||
return image === PROGRESS_IMAGE_PLACEHOLDER ? (
|
||||
<GalleryProgressImage key={PROGRESS_IMAGE_PLACEHOLDER} />
|
||||
) : (
|
||||
<HoverableImage
|
||||
key={`${image.name}-${image.thumbnail}`}
|
||||
key={`${image.image_name}-${image.thumbnail_url}`}
|
||||
image={image}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,9 @@ import {
|
||||
setCfgScale,
|
||||
setHeight,
|
||||
setImg2imgStrength,
|
||||
setNegativePrompt,
|
||||
setPerlin,
|
||||
setPrompt,
|
||||
setScheduler,
|
||||
setSeamless,
|
||||
setSeed,
|
||||
@@ -36,6 +38,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FaCopy } from 'react-icons/fa';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { filter } from 'lodash-es';
|
||||
import { Scheduler } from 'app/constants';
|
||||
|
||||
type MetadataItemProps = {
|
||||
isLink?: boolean;
|
||||
@@ -58,7 +63,6 @@ const MetadataItem = ({
|
||||
withCopy = false,
|
||||
}: MetadataItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
{onClick && (
|
||||
@@ -104,14 +108,14 @@ const MetadataItem = ({
|
||||
};
|
||||
|
||||
type ImageMetadataViewerProps = {
|
||||
image: InvokeAI.Image;
|
||||
image: ImageDTO;
|
||||
};
|
||||
|
||||
// TODO: I don't know if this is needed.
|
||||
const memoEqualityCheck = (
|
||||
prev: ImageMetadataViewerProps,
|
||||
next: ImageMetadataViewerProps
|
||||
) => prev.image.name === next.image.name;
|
||||
) => prev.image.image_name === next.image.image_name;
|
||||
|
||||
// TODO: Show more interesting information in this component.
|
||||
|
||||
@@ -128,8 +132,9 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
dispatch(setShouldShowImageDetails(false));
|
||||
});
|
||||
|
||||
const sessionId = image.metadata.invokeai?.session_id;
|
||||
const node = image.metadata.invokeai?.node as Record<string, any>;
|
||||
const sessionId = image?.session_id;
|
||||
|
||||
const metadata = image?.metadata;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { getUrl } = useGetUrl();
|
||||
@@ -154,110 +159,133 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
>
|
||||
<Flex gap={2}>
|
||||
<Text fontWeight="semibold">File:</Text>
|
||||
<Link href={getUrl(image.url)} isExternal maxW="calc(100% - 3rem)">
|
||||
{image.url.length > 64
|
||||
? image.url.substring(0, 64).concat('...')
|
||||
: image.url}
|
||||
<Link
|
||||
href={getUrl(image.image_url)}
|
||||
isExternal
|
||||
maxW="calc(100% - 3rem)"
|
||||
>
|
||||
{image.image_url.length > 64
|
||||
? image.image_url.substring(0, 64).concat('...')
|
||||
: image.image_url}
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
</Flex>
|
||||
{node && Object.keys(node).length > 0 ? (
|
||||
{metadata && Object.keys(metadata).length > 0 ? (
|
||||
<>
|
||||
{node.type && (
|
||||
<MetadataItem label="Invocation type" value={node.type} />
|
||||
{metadata.type && (
|
||||
<MetadataItem label="Invocation type" value={metadata.type} />
|
||||
)}
|
||||
{node.model && <MetadataItem label="Model" value={node.model} />}
|
||||
{node.prompt && (
|
||||
{metadata.width && (
|
||||
<MetadataItem
|
||||
label="Width"
|
||||
value={metadata.width}
|
||||
onClick={() => dispatch(setWidth(Number(metadata.width)))}
|
||||
/>
|
||||
)}
|
||||
{metadata.height && (
|
||||
<MetadataItem
|
||||
label="Height"
|
||||
value={metadata.height}
|
||||
onClick={() => dispatch(setHeight(Number(metadata.height)))}
|
||||
/>
|
||||
)}
|
||||
{metadata.model && (
|
||||
<MetadataItem label="Model" value={metadata.model} />
|
||||
)}
|
||||
{metadata.positive_conditioning && (
|
||||
<MetadataItem
|
||||
label="Prompt"
|
||||
labelPosition="top"
|
||||
value={
|
||||
typeof node.prompt === 'string'
|
||||
? node.prompt
|
||||
: promptToString(node.prompt)
|
||||
typeof metadata.positive_conditioning === 'string'
|
||||
? metadata.positive_conditioning
|
||||
: promptToString(metadata.positive_conditioning)
|
||||
}
|
||||
onClick={() => setBothPrompts(node.prompt)}
|
||||
onClick={() => setPrompt(metadata.positive_conditioning!)}
|
||||
/>
|
||||
)}
|
||||
{node.seed !== undefined && (
|
||||
{metadata.negative_conditioning && (
|
||||
<MetadataItem
|
||||
label="Prompt"
|
||||
labelPosition="top"
|
||||
value={
|
||||
typeof metadata.negative_conditioning === 'string'
|
||||
? metadata.negative_conditioning
|
||||
: promptToString(metadata.negative_conditioning)
|
||||
}
|
||||
onClick={() => setNegativePrompt(metadata.negative_conditioning!)}
|
||||
/>
|
||||
)}
|
||||
{metadata.seed !== undefined && (
|
||||
<MetadataItem
|
||||
label="Seed"
|
||||
value={node.seed}
|
||||
onClick={() => dispatch(setSeed(Number(node.seed)))}
|
||||
value={metadata.seed}
|
||||
onClick={() => dispatch(setSeed(Number(metadata.seed)))}
|
||||
/>
|
||||
)}
|
||||
{node.threshold !== undefined && (
|
||||
{/* {metadata.threshold !== undefined && (
|
||||
<MetadataItem
|
||||
label="Noise Threshold"
|
||||
value={node.threshold}
|
||||
onClick={() => dispatch(setThreshold(Number(node.threshold)))}
|
||||
value={metadata.threshold}
|
||||
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
|
||||
/>
|
||||
)}
|
||||
{node.perlin !== undefined && (
|
||||
{metadata.perlin !== undefined && (
|
||||
<MetadataItem
|
||||
label="Perlin Noise"
|
||||
value={node.perlin}
|
||||
onClick={() => dispatch(setPerlin(Number(node.perlin)))}
|
||||
value={metadata.perlin}
|
||||
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
|
||||
/>
|
||||
)}
|
||||
{node.scheduler && (
|
||||
)} */}
|
||||
{metadata.scheduler && (
|
||||
<MetadataItem
|
||||
label="Scheduler"
|
||||
value={node.scheduler}
|
||||
onClick={() => dispatch(setScheduler(node.scheduler))}
|
||||
/>
|
||||
)}
|
||||
{node.steps && (
|
||||
<MetadataItem
|
||||
label="Steps"
|
||||
value={node.steps}
|
||||
onClick={() => dispatch(setSteps(Number(node.steps)))}
|
||||
/>
|
||||
)}
|
||||
{node.cfg_scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="CFG scale"
|
||||
value={node.cfg_scale}
|
||||
onClick={() => dispatch(setCfgScale(Number(node.cfg_scale)))}
|
||||
/>
|
||||
)}
|
||||
{node.variations && node.variations.length > 0 && (
|
||||
<MetadataItem
|
||||
label="Seed-weight pairs"
|
||||
value={seedWeightsToString(node.variations)}
|
||||
value={metadata.scheduler}
|
||||
onClick={() =>
|
||||
dispatch(setSeedWeights(seedWeightsToString(node.variations)))
|
||||
dispatch(setScheduler(metadata.scheduler as Scheduler))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{node.seamless && (
|
||||
{metadata.steps && (
|
||||
<MetadataItem
|
||||
label="Steps"
|
||||
value={metadata.steps}
|
||||
onClick={() => dispatch(setSteps(Number(metadata.steps)))}
|
||||
/>
|
||||
)}
|
||||
{metadata.cfg_scale !== undefined && (
|
||||
<MetadataItem
|
||||
label="CFG scale"
|
||||
value={metadata.cfg_scale}
|
||||
onClick={() => dispatch(setCfgScale(Number(metadata.cfg_scale)))}
|
||||
/>
|
||||
)}
|
||||
{/* {metadata.variations && metadata.variations.length > 0 && (
|
||||
<MetadataItem
|
||||
label="Seed-weight pairs"
|
||||
value={seedWeightsToString(metadata.variations)}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setSeedWeights(seedWeightsToString(metadata.variations))
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{metadata.seamless && (
|
||||
<MetadataItem
|
||||
label="Seamless"
|
||||
value={node.seamless}
|
||||
onClick={() => dispatch(setSeamless(node.seamless))}
|
||||
value={metadata.seamless}
|
||||
onClick={() => dispatch(setSeamless(metadata.seamless))}
|
||||
/>
|
||||
)}
|
||||
{node.hires_fix && (
|
||||
{metadata.hires_fix && (
|
||||
<MetadataItem
|
||||
label="High Resolution Optimization"
|
||||
value={node.hires_fix}
|
||||
onClick={() => dispatch(setHiresFix(node.hires_fix))}
|
||||
value={metadata.hires_fix}
|
||||
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
|
||||
/>
|
||||
)}
|
||||
{node.width && (
|
||||
<MetadataItem
|
||||
label="Width"
|
||||
value={node.width}
|
||||
onClick={() => dispatch(setWidth(Number(node.width)))}
|
||||
/>
|
||||
)}
|
||||
{node.height && (
|
||||
<MetadataItem
|
||||
label="Height"
|
||||
value={node.height}
|
||||
onClick={() => dispatch(setHeight(Number(node.height)))}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* {init_image_path && (
|
||||
<MetadataItem
|
||||
label="Initial image"
|
||||
@@ -266,22 +294,22 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
|
||||
onClick={() => dispatch(setInitialImage(init_image_path))}
|
||||
/>
|
||||
)} */}
|
||||
{node.strength && (
|
||||
{metadata.strength && (
|
||||
<MetadataItem
|
||||
label="Image to image strength"
|
||||
value={node.strength}
|
||||
value={metadata.strength}
|
||||
onClick={() =>
|
||||
dispatch(setImg2imgStrength(Number(node.strength)))
|
||||
dispatch(setImg2imgStrength(Number(metadata.strength)))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{node.fit && (
|
||||
{/* {metadata.fit && (
|
||||
<MetadataItem
|
||||
label="Image to image fit"
|
||||
value={node.fit}
|
||||
onClick={() => dispatch(setShouldFitToWidthHeight(node.fit))}
|
||||
value={metadata.fit}
|
||||
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
) : (
|
||||
<Center width="100%" pt={10}>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
||||
import {
|
||||
receivedResultImagesPage,
|
||||
receivedUploadImagesPage,
|
||||
} from '../../../services/thunks/gallery';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
type GalleryImageObjectFitType = 'contain' | 'cover';
|
||||
|
||||
export interface GalleryState {
|
||||
selectedImage?: Image;
|
||||
selectedImage?: ImageDTO;
|
||||
galleryImageMinimumWidth: number;
|
||||
galleryImageObjectFit: GalleryImageObjectFitType;
|
||||
shouldAutoSwitchToNewImages: boolean;
|
||||
@@ -30,7 +29,7 @@ export const gallerySlice = createSlice({
|
||||
name: 'gallery',
|
||||
initialState: initialGalleryState,
|
||||
reducers: {
|
||||
imageSelected: (state, action: PayloadAction<Image | undefined>) => {
|
||||
imageSelected: (state, action: PayloadAction<ImageDTO | undefined>) => {
|
||||
state.selectedImage = action.payload;
|
||||
// TODO: if the user selects an image, disable the auto switch?
|
||||
// state.shouldAutoSwitchToNewImages = false;
|
||||
@@ -61,37 +60,18 @@ export const gallerySlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(imageReceived.fulfilled, (state, action) => {
|
||||
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
|
||||
// which is currently its own object (instead of a reference to an image in results/uploads)
|
||||
const { imagePath } = action.payload;
|
||||
const { imageName } = action.meta.arg;
|
||||
|
||||
if (state.selectedImage?.name === imageName) {
|
||||
state.selectedImage.url = imagePath;
|
||||
}
|
||||
});
|
||||
|
||||
builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
|
||||
// When we get an updated URL for an image, we need to update the selectedImage in gallery,
|
||||
// which is currently its own object (instead of a reference to an image in results/uploads)
|
||||
const { thumbnailPath } = action.payload;
|
||||
const { thumbnailName } = action.meta.arg;
|
||||
|
||||
if (state.selectedImage?.name === thumbnailName) {
|
||||
state.selectedImage.thumbnail = thumbnailPath;
|
||||
}
|
||||
});
|
||||
builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => {
|
||||
// rehydrate selectedImage URL when results list comes in
|
||||
// solves case when outdated URL is in local storage
|
||||
const selectedImage = state.selectedImage;
|
||||
if (selectedImage) {
|
||||
const selectedImageInResults = action.payload.items.find(
|
||||
(image) => image.image_name === selectedImage.name
|
||||
(image) => image.image_name === selectedImage.image_name
|
||||
);
|
||||
|
||||
if (selectedImageInResults) {
|
||||
selectedImage.url = selectedImageInResults.image_url;
|
||||
selectedImage.image_url = selectedImageInResults.image_url;
|
||||
selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url;
|
||||
state.selectedImage = selectedImage;
|
||||
}
|
||||
}
|
||||
@@ -102,10 +82,12 @@ export const gallerySlice = createSlice({
|
||||
const selectedImage = state.selectedImage;
|
||||
if (selectedImage) {
|
||||
const selectedImageInResults = action.payload.items.find(
|
||||
(image) => image.image_name === selectedImage.name
|
||||
(image) => image.image_name === selectedImage.image_name
|
||||
);
|
||||
|
||||
if (selectedImageInResults) {
|
||||
selectedImage.url = selectedImageInResults.image_url;
|
||||
selectedImage.image_url = selectedImageInResults.image_url;
|
||||
selectedImage.thumbnail_url = selectedImageInResults.thumbnail_url;
|
||||
state.selectedImage = selectedImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
|
||||
import { RootState } from 'app/store/store';
|
||||
import {
|
||||
receivedResultImagesPage,
|
||||
IMAGES_PER_PAGE,
|
||||
} from 'services/thunks/gallery';
|
||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||
import {
|
||||
imageDeleted,
|
||||
imageReceived,
|
||||
thumbnailReceived,
|
||||
imageMetadataReceived,
|
||||
imageUrlsReceived,
|
||||
} from 'services/thunks/image';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { dateComparator } from 'common/util/dateComparator';
|
||||
|
||||
export const resultsAdapter = createEntityAdapter<Image>({
|
||||
selectId: (image) => image.name,
|
||||
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
|
||||
export type ResultsImageDTO = Omit<ImageDTO, 'image_type'> & {
|
||||
image_type: 'results';
|
||||
};
|
||||
|
||||
export const resultsAdapter = createEntityAdapter<ResultsImageDTO>({
|
||||
selectId: (image) => image.image_name,
|
||||
sortComparer: (a, b) => dateComparator(b.created_at, a.created_at),
|
||||
});
|
||||
|
||||
type AdditionalResultsState = {
|
||||
@@ -53,13 +56,12 @@ const resultsSlice = createSlice({
|
||||
* Received Result Images Page - FULFILLED
|
||||
*/
|
||||
builder.addCase(receivedResultImagesPage.fulfilled, (state, action) => {
|
||||
const { items, page, pages } = action.payload;
|
||||
const { page, pages } = action.payload;
|
||||
|
||||
const resultImages = items.map((image) =>
|
||||
deserializeImageResponse(image)
|
||||
);
|
||||
// We know these will all be of the results type, but it's not represented in the API types
|
||||
const items = action.payload.items as ResultsImageDTO[];
|
||||
|
||||
resultsAdapter.setMany(state, resultImages);
|
||||
resultsAdapter.setMany(state, items);
|
||||
|
||||
state.page = page;
|
||||
state.pages = pages;
|
||||
@@ -68,33 +70,32 @@ const resultsSlice = createSlice({
|
||||
});
|
||||
|
||||
/**
|
||||
* Image Received - FULFILLED
|
||||
* Image Metadata Received - FULFILLED
|
||||
*/
|
||||
builder.addCase(imageReceived.fulfilled, (state, action) => {
|
||||
const { imagePath } = action.payload;
|
||||
const { imageName } = action.meta.arg;
|
||||
builder.addCase(imageMetadataReceived.fulfilled, (state, action) => {
|
||||
const { image_type } = action.payload;
|
||||
|
||||
resultsAdapter.updateOne(state, {
|
||||
id: imageName,
|
||||
changes: {
|
||||
url: imagePath,
|
||||
},
|
||||
});
|
||||
if (image_type === 'results') {
|
||||
resultsAdapter.upsertOne(state, action.payload as ResultsImageDTO);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Thumbnail Received - FULFILLED
|
||||
* Image URLs Received - FULFILLED
|
||||
*/
|
||||
builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
|
||||
const { thumbnailPath } = action.payload;
|
||||
const { thumbnailName } = action.meta.arg;
|
||||
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
||||
const { image_name, image_type, image_url, thumbnail_url } =
|
||||
action.payload;
|
||||
|
||||
resultsAdapter.updateOne(state, {
|
||||
id: thumbnailName,
|
||||
changes: {
|
||||
thumbnail: thumbnailPath,
|
||||
},
|
||||
});
|
||||
if (image_type === 'results') {
|
||||
resultsAdapter.updateOne(state, {
|
||||
id: image_name,
|
||||
changes: {
|
||||
image_url: image_url,
|
||||
thumbnail_url: thumbnail_url,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,12 +6,18 @@ import {
|
||||
receivedUploadImagesPage,
|
||||
IMAGES_PER_PAGE,
|
||||
} from 'services/thunks/gallery';
|
||||
import { imageDeleted } from 'services/thunks/image';
|
||||
import { imageDeleted, imageUrlsReceived } from 'services/thunks/image';
|
||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { dateComparator } from 'common/util/dateComparator';
|
||||
|
||||
export const uploadsAdapter = createEntityAdapter<Image>({
|
||||
selectId: (image) => image.name,
|
||||
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
|
||||
export type UploadsImageDTO = Omit<ImageDTO, 'image_type'> & {
|
||||
image_type: 'uploads';
|
||||
};
|
||||
|
||||
export const uploadsAdapter = createEntityAdapter<UploadsImageDTO>({
|
||||
selectId: (image) => image.image_category,
|
||||
sortComparer: (a, b) => dateComparator(b.created_at, a.created_at),
|
||||
});
|
||||
|
||||
type AdditionalUploadsState = {
|
||||
@@ -49,11 +55,12 @@ const uploadsSlice = createSlice({
|
||||
* Received Upload Images Page - FULFILLED
|
||||
*/
|
||||
builder.addCase(receivedUploadImagesPage.fulfilled, (state, action) => {
|
||||
const { items, page, pages } = action.payload;
|
||||
const { page, pages } = action.payload;
|
||||
|
||||
const images = items.map((image) => deserializeImageResponse(image));
|
||||
// We know these will all be of the uploads type, but it's not represented in the API types
|
||||
const items = action.payload.items as UploadsImageDTO[];
|
||||
|
||||
uploadsAdapter.setMany(state, images);
|
||||
uploadsAdapter.setMany(state, items);
|
||||
|
||||
state.page = page;
|
||||
state.pages = pages;
|
||||
@@ -61,6 +68,24 @@ const uploadsSlice = createSlice({
|
||||
state.isLoading = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Image URLs Received - FULFILLED
|
||||
*/
|
||||
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
||||
const { image_name, image_type, image_url, thumbnail_url } =
|
||||
action.payload;
|
||||
|
||||
if (image_type === 'uploads') {
|
||||
uploadsAdapter.updateOne(state, {
|
||||
id: image_name,
|
||||
changes: {
|
||||
image_url: image_url,
|
||||
thumbnail_url: thumbnail_url,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete Image - pending
|
||||
* Pre-emptively remove the image from the gallery
|
||||
|
||||
@@ -3,4 +3,4 @@ import { UIState } from './uiTypes';
|
||||
/**
|
||||
* UI slice persist denylist
|
||||
*/
|
||||
export const uiPersistDenylist: (keyof UIState)[] = [];
|
||||
export const uiPersistDenylist: (keyof UIState)[] = ['shouldShowImageDetails'];
|
||||
|
||||
Reference in New Issue
Block a user