mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): restore recall functionality
- Restore recall functionality to `CurrentImageButtons` and `ImageContextMenu`. - Debounce metadata requests for `ImageMetadataViewer` and `CurrentImageButtons` by 500ms. It's possible to scroll through these really fast, so we want to debounce the network requests. - `ImageContextMenu` is lazy-mounted so it does not need to be debounced; it makes the metadata request as soon as you click it. - Move next/prev image selection logic into hook and add the hotkeys for this to `CurrentImageButtons`. The hotkeys now work when metadata viewer is open. I will follow up with improved loading state during the debounced calls in the future
This commit is contained in:
parent
a43c900961
commit
6bea7bac36
@ -108,6 +108,7 @@
|
|||||||
"roarr": "^7.15.0",
|
"roarr": "^7.15.0",
|
||||||
"serialize-error": "^11.0.0",
|
"serialize-error": "^11.0.0",
|
||||||
"socket.io-client": "^4.7.0",
|
"socket.io-client": "^4.7.0",
|
||||||
|
"use-debounce": "^9.0.4",
|
||||||
"use-image": "^1.1.1",
|
"use-image": "^1.1.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AnyAction,
|
AnyAction,
|
||||||
ThunkDispatch,
|
ThunkDispatch,
|
||||||
|
autoBatchEnhancer,
|
||||||
combineReducers,
|
combineReducers,
|
||||||
configureStore,
|
configureStore,
|
||||||
} from '@reduxjs/toolkit';
|
} from '@reduxjs/toolkit';
|
||||||
@ -79,14 +80,18 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
|||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: rememberedRootReducer,
|
reducer: rememberedRootReducer,
|
||||||
enhancers: [
|
enhancers: (existingEnhancers) => {
|
||||||
|
return existingEnhancers
|
||||||
|
.concat(
|
||||||
rememberEnhancer(window.localStorage, rememberedKeys, {
|
rememberEnhancer(window.localStorage, rememberedKeys, {
|
||||||
persistDebounce: 300,
|
persistDebounce: 300,
|
||||||
serialize,
|
serialize,
|
||||||
unserialize,
|
unserialize,
|
||||||
prefix: LOCALSTORAGE_PREFIX,
|
prefix: LOCALSTORAGE_PREFIX,
|
||||||
}),
|
})
|
||||||
],
|
)
|
||||||
|
.concat(autoBatchEnhancer());
|
||||||
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
immutableCheck: false,
|
immutableCheck: false,
|
||||||
|
@ -45,7 +45,11 @@ import {
|
|||||||
FaShare,
|
FaShare,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import {
|
||||||
|
useGetImageDTOQuery,
|
||||||
|
useGetImageMetadataQuery,
|
||||||
|
} from 'services/api/endpoints/images';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
@ -128,10 +132,23 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||||
useRecallParameters();
|
useRecallParameters();
|
||||||
|
|
||||||
const { currentData: image } = useGetImageDTOQuery(
|
const [debouncedMetadataQueryArg, debounceState] = useDebounce(
|
||||||
|
lastSelectedImage,
|
||||||
|
500
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentData: image, isFetching } = useGetImageDTOQuery(
|
||||||
lastSelectedImage ?? skipToken
|
lastSelectedImage ?? skipToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { currentData: metadataData } = useGetImageMetadataQuery(
|
||||||
|
debounceState.isPending()
|
||||||
|
? skipToken
|
||||||
|
: debouncedMetadataQueryArg ?? skipToken
|
||||||
|
);
|
||||||
|
|
||||||
|
const metadata = metadataData?.metadata;
|
||||||
|
|
||||||
// const handleCopyImage = useCallback(async () => {
|
// const handleCopyImage = useCallback(async () => {
|
||||||
// if (!image?.url) {
|
// if (!image?.url) {
|
||||||
// return;
|
// return;
|
||||||
@ -193,29 +210,26 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
}, [toaster, t, image]);
|
}, [toaster, t, image]);
|
||||||
|
|
||||||
const handleClickUseAllParameters = useCallback(() => {
|
const handleClickUseAllParameters = useCallback(() => {
|
||||||
recallAllParameters(image);
|
recallAllParameters(metadata);
|
||||||
}, [image, recallAllParameters]);
|
}, [metadata, recallAllParameters]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'a',
|
'a',
|
||||||
() => {
|
() => {
|
||||||
handleClickUseAllParameters;
|
handleClickUseAllParameters;
|
||||||
},
|
},
|
||||||
[image, recallAllParameters]
|
[metadata, recallAllParameters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUseSeed = useCallback(() => {
|
const handleUseSeed = useCallback(() => {
|
||||||
recallSeed(image?.metadata?.seed);
|
recallSeed(metadata?.seed);
|
||||||
}, [image, recallSeed]);
|
}, [metadata?.seed, recallSeed]);
|
||||||
|
|
||||||
useHotkeys('s', handleUseSeed, [image]);
|
useHotkeys('s', handleUseSeed, [image]);
|
||||||
|
|
||||||
const handleUsePrompt = useCallback(() => {
|
const handleUsePrompt = useCallback(() => {
|
||||||
recallBothPrompts(
|
recallBothPrompts(metadata?.positive_prompt, metadata?.negative_prompt);
|
||||||
image?.metadata?.positive_conditioning,
|
}, [metadata?.negative_prompt, metadata?.positive_prompt, recallBothPrompts]);
|
||||||
image?.metadata?.negative_conditioning
|
|
||||||
);
|
|
||||||
}, [image, recallBothPrompts]);
|
|
||||||
|
|
||||||
useHotkeys('p', handleUsePrompt, [image]);
|
useHotkeys('p', handleUsePrompt, [image]);
|
||||||
|
|
||||||
@ -440,7 +454,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
icon={<FaQuoteRight />}
|
icon={<FaQuoteRight />}
|
||||||
tooltip={`${t('parameters.usePrompt')} (P)`}
|
tooltip={`${t('parameters.usePrompt')} (P)`}
|
||||||
aria-label={`${t('parameters.usePrompt')} (P)`}
|
aria-label={`${t('parameters.usePrompt')} (P)`}
|
||||||
isDisabled={!image?.metadata?.positive_conditioning}
|
isDisabled={!metadata?.positive_prompt}
|
||||||
onClick={handleUsePrompt}
|
onClick={handleUsePrompt}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -448,7 +462,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
icon={<FaSeedling />}
|
icon={<FaSeedling />}
|
||||||
tooltip={`${t('parameters.useSeed')} (S)`}
|
tooltip={`${t('parameters.useSeed')} (S)`}
|
||||||
aria-label={`${t('parameters.useSeed')} (S)`}
|
aria-label={`${t('parameters.useSeed')} (S)`}
|
||||||
isDisabled={!image?.metadata?.seed}
|
isDisabled={!metadata?.seed}
|
||||||
onClick={handleUseSeed}
|
onClick={handleUseSeed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -456,10 +470,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
icon={<FaAsterisk />}
|
icon={<FaAsterisk />}
|
||||||
tooltip={`${t('parameters.useAll')} (A)`}
|
tooltip={`${t('parameters.useAll')} (A)`}
|
||||||
aria-label={`${t('parameters.useAll')} (A)`}
|
aria-label={`${t('parameters.useAll')} (A)`}
|
||||||
isDisabled={
|
isDisabled={!metadata}
|
||||||
// not sure what this list should be
|
|
||||||
!['t2l', 'l2l', 'inpaint'].includes(String(image?.metadata?.type))
|
|
||||||
}
|
|
||||||
onClick={handleClickUseAllParameters}
|
onClick={handleClickUseAllParameters}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -11,7 +11,9 @@ import IAIDndImage from 'common/components/IAIDndImage';
|
|||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
import { useNextPrevImage } from '../hooks/useNextPrevImage';
|
||||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||||
import NextPrevImageButtons from './NextPrevImageButtons';
|
import NextPrevImageButtons from './NextPrevImageButtons';
|
||||||
|
|
||||||
@ -49,6 +51,45 @@ const CurrentImagePreview = () => {
|
|||||||
shouldAntialiasProgressImage,
|
shouldAntialiasProgressImage,
|
||||||
} = useAppSelector(imagesSelector);
|
} = useAppSelector(imagesSelector);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handlePrevImage,
|
||||||
|
handleNextImage,
|
||||||
|
prevImageId,
|
||||||
|
nextImageId,
|
||||||
|
isOnLastImage,
|
||||||
|
handleLoadMoreImages,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
isFetching,
|
||||||
|
} = useNextPrevImage();
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'left',
|
||||||
|
() => {
|
||||||
|
handlePrevImage();
|
||||||
|
},
|
||||||
|
[prevImageId]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'right',
|
||||||
|
() => {
|
||||||
|
if (isOnLastImage && areMoreImagesAvailable && !isFetching) {
|
||||||
|
handleLoadMoreImages();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isOnLastImage) {
|
||||||
|
handleNextImage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
nextImageId,
|
||||||
|
isOnLastImage,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
handleLoadMoreImages,
|
||||||
|
isFetching,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentData: imageDTO,
|
currentData: imageDTO,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
@ -6,10 +6,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
||||||
import {
|
import { imagesAddedToBatch } from 'features/batch/store/batchSlice';
|
||||||
imagesAddedToBatch,
|
|
||||||
selectionAddedToBatch,
|
|
||||||
} from 'features/batch/store/batchSlice';
|
|
||||||
import {
|
import {
|
||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
setInitialCanvasImage,
|
setInitialCanvasImage,
|
||||||
@ -24,6 +21,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa';
|
import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
||||||
|
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||||
@ -38,24 +36,17 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
() =>
|
() =>
|
||||||
createSelector(
|
createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
({ gallery, batch }) => {
|
({ gallery }) => {
|
||||||
const selectionCount = gallery.selection.length;
|
const selectionCount = gallery.selection.length;
|
||||||
const isInBatch = batch.imageNames.includes(image.image_name);
|
|
||||||
|
|
||||||
return { selectionCount, isInBatch };
|
return { selectionCount };
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
),
|
),
|
||||||
[image.image_name]
|
[]
|
||||||
);
|
);
|
||||||
const { selectionCount, isInBatch } = useAppSelector(selector);
|
const { selectionCount } = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const toaster = useAppToaster();
|
|
||||||
|
|
||||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
|
||||||
|
|
||||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||||
|
|
||||||
@ -66,178 +57,17 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
dispatch(imageToDeleteSelected(image));
|
dispatch(imageToDeleteSelected(image));
|
||||||
}, [dispatch, image]);
|
}, [dispatch, image]);
|
||||||
|
|
||||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
|
||||||
useRecallParameters();
|
|
||||||
|
|
||||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
|
||||||
|
|
||||||
// Recall parameters handlers
|
|
||||||
const handleRecallPrompt = useCallback(() => {
|
|
||||||
recallBothPrompts(
|
|
||||||
image.metadata?.positive_conditioning,
|
|
||||||
image.metadata?.negative_conditioning
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
image.metadata?.negative_conditioning,
|
|
||||||
image.metadata?.positive_conditioning,
|
|
||||||
recallBothPrompts,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleRecallSeed = useCallback(() => {
|
|
||||||
recallSeed(image.metadata?.seed);
|
|
||||||
}, [image, recallSeed]);
|
|
||||||
|
|
||||||
const handleSendToImageToImage = useCallback(() => {
|
|
||||||
dispatch(sentImageToImg2Img());
|
|
||||||
dispatch(initialImageSelected(image));
|
|
||||||
}, [dispatch, image]);
|
|
||||||
|
|
||||||
// const handleRecallInitialImage = useCallback(() => {
|
|
||||||
// recallInitialImage(image.metadata.invokeai?.node?.image);
|
|
||||||
// }, [image, recallInitialImage]);
|
|
||||||
|
|
||||||
const handleSendToCanvas = () => {
|
|
||||||
dispatch(sentImageToCanvas());
|
|
||||||
dispatch(setInitialCanvasImage(image));
|
|
||||||
dispatch(resizeAndScaleCanvas());
|
|
||||||
dispatch(setActiveTab('unifiedCanvas'));
|
|
||||||
|
|
||||||
toaster({
|
|
||||||
title: t('toast.sentToUnifiedCanvas'),
|
|
||||||
status: 'success',
|
|
||||||
duration: 2500,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUseAllParameters = useCallback(() => {
|
|
||||||
recallAllParameters(image);
|
|
||||||
}, [image, recallAllParameters]);
|
|
||||||
|
|
||||||
const handleLightBox = () => {
|
|
||||||
// dispatch(setCurrentImage(image));
|
|
||||||
// dispatch(setIsLightboxOpen(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddToBoard = useCallback(() => {
|
const handleAddToBoard = useCallback(() => {
|
||||||
onClickAddToBoard(image);
|
onClickAddToBoard(image);
|
||||||
}, [image, onClickAddToBoard]);
|
}, [image, onClickAddToBoard]);
|
||||||
|
|
||||||
const handleRemoveFromBoard = useCallback(() => {
|
|
||||||
if (!image.board_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeFromBoard({ board_id: image.board_id, image_name: image.image_name });
|
|
||||||
}, [image.board_id, image.image_name, removeFromBoard]);
|
|
||||||
|
|
||||||
const handleOpenInNewTab = () => {
|
|
||||||
window.open(image.image_url, '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddSelectionToBatch = useCallback(() => {
|
|
||||||
dispatch(selectionAddedToBatch());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const handleAddToBatch = useCallback(() => {
|
|
||||||
dispatch(imagesAddedToBatch([image.image_name]));
|
|
||||||
}, [dispatch, image.image_name]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu<HTMLDivElement>
|
<ContextMenu<HTMLDivElement>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
renderMenu={() => (
|
renderMenu={() => (
|
||||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||||
{selectionCount === 1 ? (
|
{selectionCount === 1 ? (
|
||||||
<>
|
<SingleSelectionMenuItems image={image} />
|
||||||
<MenuItem
|
|
||||||
icon={<ExternalLinkIcon />}
|
|
||||||
onClickCapture={handleOpenInNewTab}
|
|
||||||
>
|
|
||||||
{t('common.openInNewTab')}
|
|
||||||
</MenuItem>
|
|
||||||
{isLightboxEnabled && (
|
|
||||||
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
|
||||||
{t('parameters.openInViewer')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleRecallPrompt}
|
|
||||||
isDisabled={
|
|
||||||
image?.metadata?.positive_conditioning === undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('parameters.usePrompt')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleRecallSeed}
|
|
||||||
isDisabled={image?.metadata?.seed === undefined}
|
|
||||||
>
|
|
||||||
{t('parameters.useSeed')}
|
|
||||||
</MenuItem>
|
|
||||||
{/* <MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleRecallInitialImage}
|
|
||||||
isDisabled={image?.metadata?.type !== 'img2img'}
|
|
||||||
>
|
|
||||||
{t('parameters.useInitImg')}
|
|
||||||
</MenuItem> */}
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleUseAllParameters}
|
|
||||||
isDisabled={
|
|
||||||
// what should these be
|
|
||||||
!['t2l', 'l2l', 'inpaint'].includes(
|
|
||||||
String(image?.metadata?.type)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('parameters.useAll')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={<FaShare />}
|
|
||||||
onClickCapture={handleSendToImageToImage}
|
|
||||||
id="send-to-img2img"
|
|
||||||
>
|
|
||||||
{t('parameters.sendToImg2Img')}
|
|
||||||
</MenuItem>
|
|
||||||
{isCanvasEnabled && (
|
|
||||||
<MenuItem
|
|
||||||
icon={<FaShare />}
|
|
||||||
onClickCapture={handleSendToCanvas}
|
|
||||||
id="send-to-canvas"
|
|
||||||
>
|
|
||||||
{t('parameters.sendToUnifiedCanvas')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{/* <MenuItem
|
|
||||||
icon={<FaFolder />}
|
|
||||||
isDisabled={isInBatch}
|
|
||||||
onClickCapture={handleAddToBatch}
|
|
||||||
>
|
|
||||||
Add to Batch
|
|
||||||
</MenuItem> */}
|
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
|
||||||
{image.board_id ? 'Change Board' : 'Add to Board'}
|
|
||||||
</MenuItem>
|
|
||||||
{image.board_id && (
|
|
||||||
<MenuItem
|
|
||||||
icon={<FaFolder />}
|
|
||||||
onClickCapture={handleRemoveFromBoard}
|
|
||||||
>
|
|
||||||
Remove from Board
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuItem
|
|
||||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
|
||||||
icon={<FaTrash />}
|
|
||||||
onClickCapture={handleDelete}
|
|
||||||
>
|
|
||||||
{t('gallery.deleteImage')}
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@ -271,3 +101,185 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ImageContextMenu);
|
export default memo(ImageContextMenu);
|
||||||
|
|
||||||
|
type SingleSelectionMenuItemsProps = {
|
||||||
|
image: ImageDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||||
|
const { image } = props;
|
||||||
|
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
[stateSelector],
|
||||||
|
({ batch }) => {
|
||||||
|
const isInBatch = batch.imageNames.includes(image.image_name);
|
||||||
|
|
||||||
|
return { isInBatch };
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[image.image_name]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { isInBatch } = useAppSelector(selector);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
|
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||||
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
|
|
||||||
|
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||||
|
|
||||||
|
const { currentData } = useGetImageMetadataQuery(image.image_name);
|
||||||
|
|
||||||
|
const metadata = currentData?.metadata;
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
if (!image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(imageToDeleteSelected(image));
|
||||||
|
}, [dispatch, image]);
|
||||||
|
|
||||||
|
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||||
|
useRecallParameters();
|
||||||
|
|
||||||
|
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||||
|
|
||||||
|
// Recall parameters handlers
|
||||||
|
const handleRecallPrompt = useCallback(() => {
|
||||||
|
recallBothPrompts(metadata?.positive_prompt, metadata?.negative_prompt);
|
||||||
|
}, [metadata?.negative_prompt, metadata?.positive_prompt, recallBothPrompts]);
|
||||||
|
|
||||||
|
const handleRecallSeed = useCallback(() => {
|
||||||
|
recallSeed(metadata?.seed);
|
||||||
|
}, [metadata?.seed, recallSeed]);
|
||||||
|
|
||||||
|
const handleSendToImageToImage = useCallback(() => {
|
||||||
|
dispatch(sentImageToImg2Img());
|
||||||
|
dispatch(initialImageSelected(image));
|
||||||
|
}, [dispatch, image]);
|
||||||
|
|
||||||
|
const handleSendToCanvas = () => {
|
||||||
|
dispatch(sentImageToCanvas());
|
||||||
|
dispatch(setInitialCanvasImage(image));
|
||||||
|
dispatch(resizeAndScaleCanvas());
|
||||||
|
dispatch(setActiveTab('unifiedCanvas'));
|
||||||
|
|
||||||
|
toaster({
|
||||||
|
title: t('toast.sentToUnifiedCanvas'),
|
||||||
|
status: 'success',
|
||||||
|
duration: 2500,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUseAllParameters = useCallback(() => {
|
||||||
|
console.log(metadata);
|
||||||
|
recallAllParameters(metadata);
|
||||||
|
}, [metadata, recallAllParameters]);
|
||||||
|
|
||||||
|
const handleLightBox = () => {
|
||||||
|
// dispatch(setCurrentImage(image));
|
||||||
|
// dispatch(setIsLightboxOpen(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToBoard = useCallback(() => {
|
||||||
|
onClickAddToBoard(image);
|
||||||
|
}, [image, onClickAddToBoard]);
|
||||||
|
|
||||||
|
const handleRemoveFromBoard = useCallback(() => {
|
||||||
|
if (!image.board_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeFromBoard({ board_id: image.board_id, image_name: image.image_name });
|
||||||
|
}, [image.board_id, image.image_name, removeFromBoard]);
|
||||||
|
|
||||||
|
const handleOpenInNewTab = () => {
|
||||||
|
window.open(image.image_url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToBatch = useCallback(() => {
|
||||||
|
dispatch(imagesAddedToBatch([image.image_name]));
|
||||||
|
}, [dispatch, image.image_name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem icon={<ExternalLinkIcon />} onClickCapture={handleOpenInNewTab}>
|
||||||
|
{t('common.openInNewTab')}
|
||||||
|
</MenuItem>
|
||||||
|
{isLightboxEnabled && (
|
||||||
|
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
||||||
|
{t('parameters.openInViewer')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleRecallPrompt}
|
||||||
|
isDisabled={
|
||||||
|
metadata?.positive_prompt === undefined &&
|
||||||
|
metadata?.negative_prompt === undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('parameters.usePrompt')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleRecallSeed}
|
||||||
|
isDisabled={metadata?.seed === undefined}
|
||||||
|
>
|
||||||
|
{t('parameters.useSeed')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleUseAllParameters}
|
||||||
|
isDisabled={!metadata}
|
||||||
|
>
|
||||||
|
{t('parameters.useAll')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaShare />}
|
||||||
|
onClickCapture={handleSendToImageToImage}
|
||||||
|
id="send-to-img2img"
|
||||||
|
>
|
||||||
|
{t('parameters.sendToImg2Img')}
|
||||||
|
</MenuItem>
|
||||||
|
{isCanvasEnabled && (
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaShare />}
|
||||||
|
onClickCapture={handleSendToCanvas}
|
||||||
|
id="send-to-canvas"
|
||||||
|
>
|
||||||
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaFolder />}
|
||||||
|
isDisabled={isInBatch}
|
||||||
|
onClickCapture={handleAddToBatch}
|
||||||
|
>
|
||||||
|
Add to Batch
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||||
|
{image.board_id ? 'Change Board' : 'Add to Board'}
|
||||||
|
</MenuItem>
|
||||||
|
{image.board_id && (
|
||||||
|
<MenuItem icon={<FaFolder />} onClickCapture={handleRemoveFromBoard}>
|
||||||
|
Remove from Board
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
onClickCapture={handleDelete}
|
||||||
|
>
|
||||||
|
{t('gallery.deleteImage')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -13,6 +13,7 @@ import { skipToken } from '@reduxjs/toolkit/dist/query';
|
|||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
import { useGetImageMetadataQuery } from 'services/api/endpoints/images';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
import ImageMetadataActions from './ImageMetadataActions';
|
import ImageMetadataActions from './ImageMetadataActions';
|
||||||
import MetadataJSONViewer from './MetadataJSONViewer';
|
import MetadataJSONViewer from './MetadataJSONViewer';
|
||||||
|
|
||||||
@ -27,16 +28,26 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
// dispatch(setShouldShowImageDetails(false));
|
// dispatch(setShouldShowImageDetails(false));
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const { data } = useGetImageMetadataQuery(image?.image_name ?? skipToken);
|
const [debouncedMetadataQueryArg, debounceState] = useDebounce(
|
||||||
const metadata = data?.metadata;
|
image.image_name,
|
||||||
|
500
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentData } = useGetImageMetadataQuery(
|
||||||
|
debounceState.isPending()
|
||||||
|
? skipToken
|
||||||
|
: debouncedMetadataQueryArg ?? skipToken
|
||||||
|
);
|
||||||
|
const metadata = currentData?.metadata;
|
||||||
|
const graph = currentData?.graph;
|
||||||
|
|
||||||
const tabData = useMemo(() => {
|
const tabData = useMemo(() => {
|
||||||
const _tabData: { label: string; data: object; copyTooltip: string }[] = [];
|
const _tabData: { label: string; data: object; copyTooltip: string }[] = [];
|
||||||
|
|
||||||
if (data?.metadata) {
|
if (metadata) {
|
||||||
_tabData.push({
|
_tabData.push({
|
||||||
label: 'Core Metadata',
|
label: 'Core Metadata',
|
||||||
data: data?.metadata,
|
data: metadata,
|
||||||
copyTooltip: 'Copy Core Metadata JSON',
|
copyTooltip: 'Copy Core Metadata JSON',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -49,15 +60,15 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.graph) {
|
if (graph) {
|
||||||
_tabData.push({
|
_tabData.push({
|
||||||
label: 'Graph',
|
label: 'Graph',
|
||||||
data: data?.graph,
|
data: graph,
|
||||||
copyTooltip: 'Copy Graph JSON',
|
copyTooltip: 'Copy Graph JSON',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return _tabData;
|
return _tabData;
|
||||||
}, [data?.metadata, data?.graph, image]);
|
}, [metadata, graph, image]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -1,18 +1,8 @@
|
|||||||
import { ChakraProps, Flex, Grid, IconButton, Spinner } from '@chakra-ui/react';
|
import { ChakraProps, Flex, Grid, IconButton, Spinner } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import {
|
|
||||||
imageSelected,
|
|
||||||
selectFilteredImages,
|
|
||||||
selectImagesById,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
|
||||||
import { memo, useCallback, useState } from 'react';
|
import { memo, useCallback, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
import { useNextPrevImage } from '../hooks/useNextPrevImage';
|
||||||
|
|
||||||
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -24,74 +14,18 @@ const nextPrevButtonStyles: ChakraProps['sx'] = {
|
|||||||
color: 'base.100',
|
color: 'base.100',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nextPrevImageButtonsSelector = createSelector(
|
|
||||||
[stateSelector, selectFilteredImages],
|
|
||||||
(state, filteredImages) => {
|
|
||||||
const { total, isFetching } = state.gallery;
|
|
||||||
const lastSelectedImage =
|
|
||||||
state.gallery.selection[state.gallery.selection.length - 1];
|
|
||||||
|
|
||||||
if (!lastSelectedImage || filteredImages.length === 0) {
|
|
||||||
return {
|
|
||||||
isOnFirstImage: true,
|
|
||||||
isOnLastImage: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentImageIndex = filteredImages.findIndex(
|
|
||||||
(i) => i.image_name === lastSelectedImage
|
|
||||||
);
|
|
||||||
const nextImageIndex = clamp(
|
|
||||||
currentImageIndex + 1,
|
|
||||||
0,
|
|
||||||
filteredImages.length - 1
|
|
||||||
);
|
|
||||||
|
|
||||||
const prevImageIndex = clamp(
|
|
||||||
currentImageIndex - 1,
|
|
||||||
0,
|
|
||||||
filteredImages.length - 1
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextImageId = filteredImages[nextImageIndex].image_name;
|
|
||||||
const prevImageId = filteredImages[prevImageIndex].image_name;
|
|
||||||
|
|
||||||
const nextImage = selectImagesById(state, nextImageId);
|
|
||||||
const prevImage = selectImagesById(state, prevImageId);
|
|
||||||
|
|
||||||
const imagesLength = filteredImages.length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isOnFirstImage: currentImageIndex === 0,
|
|
||||||
isOnLastImage:
|
|
||||||
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
|
||||||
areMoreImagesAvailable: total > imagesLength,
|
|
||||||
isFetching,
|
|
||||||
nextImage,
|
|
||||||
prevImage,
|
|
||||||
nextImageId,
|
|
||||||
prevImageId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const NextPrevImageButtons = () => {
|
const NextPrevImageButtons = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
handlePrevImage,
|
||||||
|
handleNextImage,
|
||||||
isOnFirstImage,
|
isOnFirstImage,
|
||||||
isOnLastImage,
|
isOnLastImage,
|
||||||
nextImageId,
|
handleLoadMoreImages,
|
||||||
prevImageId,
|
|
||||||
areMoreImagesAvailable,
|
areMoreImagesAvailable,
|
||||||
isFetching,
|
isFetching,
|
||||||
} = useAppSelector(nextPrevImageButtonsSelector);
|
} = useNextPrevImage();
|
||||||
|
|
||||||
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -104,50 +38,6 @@ const NextPrevImageButtons = () => {
|
|||||||
setShouldShowNextPrevButtons(false);
|
setShouldShowNextPrevButtons(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePrevImage = useCallback(() => {
|
|
||||||
prevImageId && dispatch(imageSelected(prevImageId));
|
|
||||||
}, [dispatch, prevImageId]);
|
|
||||||
|
|
||||||
const handleNextImage = useCallback(() => {
|
|
||||||
nextImageId && dispatch(imageSelected(nextImageId));
|
|
||||||
}, [dispatch, nextImageId]);
|
|
||||||
|
|
||||||
const handleLoadMoreImages = useCallback(() => {
|
|
||||||
dispatch(
|
|
||||||
receivedPageOfImages({
|
|
||||||
is_intermediate: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'left',
|
|
||||||
() => {
|
|
||||||
handlePrevImage();
|
|
||||||
},
|
|
||||||
[prevImageId]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'right',
|
|
||||||
() => {
|
|
||||||
if (isOnLastImage && areMoreImagesAvailable && !isFetching) {
|
|
||||||
handleLoadMoreImages();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isOnLastImage) {
|
|
||||||
handleNextImage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
nextImageId,
|
|
||||||
isOnLastImage,
|
|
||||||
areMoreImagesAvailable,
|
|
||||||
handleLoadMoreImages,
|
|
||||||
isFetching,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
imageSelected,
|
||||||
|
selectFilteredImages,
|
||||||
|
selectImagesById,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||||
|
|
||||||
|
export const nextPrevImageButtonsSelector = createSelector(
|
||||||
|
[stateSelector, selectFilteredImages],
|
||||||
|
(state, filteredImages) => {
|
||||||
|
const { total, isFetching } = state.gallery;
|
||||||
|
const lastSelectedImage =
|
||||||
|
state.gallery.selection[state.gallery.selection.length - 1];
|
||||||
|
|
||||||
|
if (!lastSelectedImage || filteredImages.length === 0) {
|
||||||
|
return {
|
||||||
|
isOnFirstImage: true,
|
||||||
|
isOnLastImage: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentImageIndex = filteredImages.findIndex(
|
||||||
|
(i) => i.image_name === lastSelectedImage
|
||||||
|
);
|
||||||
|
const nextImageIndex = clamp(
|
||||||
|
currentImageIndex + 1,
|
||||||
|
0,
|
||||||
|
filteredImages.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
const prevImageIndex = clamp(
|
||||||
|
currentImageIndex - 1,
|
||||||
|
0,
|
||||||
|
filteredImages.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextImageId = filteredImages[nextImageIndex].image_name;
|
||||||
|
const prevImageId = filteredImages[prevImageIndex].image_name;
|
||||||
|
|
||||||
|
const nextImage = selectImagesById(state, nextImageId);
|
||||||
|
const prevImage = selectImagesById(state, prevImageId);
|
||||||
|
|
||||||
|
const imagesLength = filteredImages.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOnFirstImage: currentImageIndex === 0,
|
||||||
|
isOnLastImage:
|
||||||
|
!isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
|
||||||
|
areMoreImagesAvailable: total > imagesLength,
|
||||||
|
isFetching,
|
||||||
|
nextImage,
|
||||||
|
prevImage,
|
||||||
|
nextImageId,
|
||||||
|
prevImageId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useNextPrevImage = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOnFirstImage,
|
||||||
|
isOnLastImage,
|
||||||
|
nextImageId,
|
||||||
|
prevImageId,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
isFetching,
|
||||||
|
} = useAppSelector(nextPrevImageButtonsSelector);
|
||||||
|
|
||||||
|
const handlePrevImage = useCallback(() => {
|
||||||
|
prevImageId && dispatch(imageSelected(prevImageId));
|
||||||
|
}, [dispatch, prevImageId]);
|
||||||
|
|
||||||
|
const handleNextImage = useCallback(() => {
|
||||||
|
nextImageId && dispatch(imageSelected(nextImageId));
|
||||||
|
}, [dispatch, nextImageId]);
|
||||||
|
|
||||||
|
const handleLoadMoreImages = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
receivedPageOfImages({
|
||||||
|
is_intermediate: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePrevImage,
|
||||||
|
handleNextImage,
|
||||||
|
isOnFirstImage,
|
||||||
|
isOnLastImage,
|
||||||
|
nextImageId,
|
||||||
|
prevImageId,
|
||||||
|
areMoreImagesAvailable,
|
||||||
|
handleLoadMoreImages,
|
||||||
|
isFetching,
|
||||||
|
};
|
||||||
|
};
|
@ -2,6 +2,7 @@ import { useAppToaster } from 'app/components/Toaster';
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { UnsafeImageMetadata } from 'services/api/endpoints/images';
|
||||||
import { isImageField } from 'services/api/guards';
|
import { isImageField } from 'services/api/guards';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { initialImageSelected, modelSelected } from '../store/actions';
|
import { initialImageSelected, modelSelected } from '../store/actions';
|
||||||
@ -269,28 +270,24 @@ export const useRecallParameters = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const recallAllParameters = useCallback(
|
const recallAllParameters = useCallback(
|
||||||
(image: ImageDTO | undefined) => {
|
(metadata: UnsafeImageMetadata['metadata'] | undefined) => {
|
||||||
if (!image || !image.metadata) {
|
if (!metadata) {
|
||||||
allParameterNotSetToast();
|
allParameterNotSetToast();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cfg_scale,
|
cfg_scale,
|
||||||
height,
|
height,
|
||||||
model,
|
model,
|
||||||
positive_conditioning,
|
positive_prompt,
|
||||||
negative_conditioning,
|
negative_prompt,
|
||||||
scheduler,
|
scheduler,
|
||||||
seed,
|
seed,
|
||||||
steps,
|
steps,
|
||||||
width,
|
width,
|
||||||
strength,
|
strength,
|
||||||
clip,
|
} = metadata;
|
||||||
extra,
|
|
||||||
latents,
|
|
||||||
unet,
|
|
||||||
vae,
|
|
||||||
} = image.metadata;
|
|
||||||
|
|
||||||
if (isValidCfgScale(cfg_scale)) {
|
if (isValidCfgScale(cfg_scale)) {
|
||||||
dispatch(setCfgScale(cfg_scale));
|
dispatch(setCfgScale(cfg_scale));
|
||||||
@ -298,11 +295,11 @@ export const useRecallParameters = () => {
|
|||||||
if (isValidMainModel(model)) {
|
if (isValidMainModel(model)) {
|
||||||
dispatch(modelSelected(model));
|
dispatch(modelSelected(model));
|
||||||
}
|
}
|
||||||
if (isValidPositivePrompt(positive_conditioning)) {
|
if (isValidPositivePrompt(positive_prompt)) {
|
||||||
dispatch(setPositivePrompt(positive_conditioning));
|
dispatch(setPositivePrompt(positive_prompt));
|
||||||
}
|
}
|
||||||
if (isValidNegativePrompt(negative_conditioning)) {
|
if (isValidNegativePrompt(negative_prompt)) {
|
||||||
dispatch(setNegativePrompt(negative_conditioning));
|
dispatch(setNegativePrompt(negative_prompt));
|
||||||
}
|
}
|
||||||
if (isValidScheduler(scheduler)) {
|
if (isValidScheduler(scheduler)) {
|
||||||
dispatch(setScheduler(scheduler));
|
dispatch(setScheduler(scheduler));
|
||||||
|
@ -6409,6 +6409,11 @@ use-composed-ref@^1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
|
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
|
||||||
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
|
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
|
||||||
|
|
||||||
|
use-debounce@^9.0.4:
|
||||||
|
version "9.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
|
||||||
|
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
|
||||||
|
|
||||||
use-image@^1.1.1:
|
use-image@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/use-image/-/use-image-1.1.1.tgz#bdd3f2e1718393ffc0e56136f993467103d9d2df"
|
resolved "https://registry.yarnpkg.com/use-image/-/use-image-1.1.1.tgz#bdd3f2e1718393ffc0e56136f993467103d9d2df"
|
||||||
|
Loading…
Reference in New Issue
Block a user