mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-05 15:55:00 -05:00
230 lines
7.5 KiB
TypeScript
230 lines
7.5 KiB
TypeScript
import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library';
|
|
import { skipToken } from '@reduxjs/toolkit/query';
|
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
import IAIDndImage from 'common/components/IAIDndImage';
|
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
|
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
|
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
|
import type { CanvasControlAdapterState } from 'features/controlLayers/store/types';
|
|
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
|
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
|
import {
|
|
useAddImageToBoardMutation,
|
|
useChangeImageIsIntermediateMutation,
|
|
useGetImageDTOQuery,
|
|
useRemoveImageFromBoardMutation,
|
|
} from 'services/api/endpoints/images';
|
|
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
|
|
|
type Props = {
|
|
controlAdapter: CanvasControlAdapterState;
|
|
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
|
droppableData: TypesafeDroppableData;
|
|
postUploadAction: PostUploadAction;
|
|
onErrorLoadingImage: () => void;
|
|
onErrorLoadingProcessedImage: () => void;
|
|
};
|
|
|
|
export const ControlAdapterImagePreview = memo(
|
|
({
|
|
controlAdapter,
|
|
onChangeImage,
|
|
droppableData,
|
|
postUploadAction,
|
|
onErrorLoadingImage,
|
|
onErrorLoadingProcessedImage,
|
|
}: Props) => {
|
|
const { t } = useTranslation();
|
|
const dispatch = useAppDispatch();
|
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
|
const isConnected = useAppSelector((s) => s.system.isConnected);
|
|
const optimalDimension = useAppSelector(selectOptimalDimension);
|
|
const shift = useShiftModifier();
|
|
|
|
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
|
|
|
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
|
controlAdapter.imageObject?.image.image_name ?? skipToken
|
|
);
|
|
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
|
controlAdapter.processedImageObject?.image.image_name ?? skipToken
|
|
);
|
|
|
|
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
|
const [addToBoard] = useAddImageToBoardMutation();
|
|
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
|
const handleResetControlImage = useCallback(() => {
|
|
onChangeImage(null);
|
|
}, [onChangeImage]);
|
|
|
|
const handleSaveControlImage = useCallback(async () => {
|
|
if (!processedControlImage) {
|
|
return;
|
|
}
|
|
|
|
await changeIsIntermediate({
|
|
imageDTO: processedControlImage,
|
|
is_intermediate: false,
|
|
}).unwrap();
|
|
|
|
if (autoAddBoardId !== 'none') {
|
|
addToBoard({
|
|
imageDTO: processedControlImage,
|
|
board_id: autoAddBoardId,
|
|
});
|
|
} else {
|
|
removeFromBoard({ imageDTO: processedControlImage });
|
|
}
|
|
}, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]);
|
|
|
|
const handleSetControlImageToDimensions = useCallback(() => {
|
|
if (!controlImage) {
|
|
return;
|
|
}
|
|
|
|
const options = { updateAspectRatio: true, clamp: true };
|
|
|
|
if (shift) {
|
|
const { width, height } = controlImage;
|
|
dispatch(bboxWidthChanged({ width, ...options }));
|
|
dispatch(bboxHeightChanged({ height, ...options }));
|
|
} else {
|
|
const { width, height } = calculateNewSize(
|
|
controlImage.width / controlImage.height,
|
|
optimalDimension * optimalDimension
|
|
);
|
|
dispatch(bboxWidthChanged({ width, ...options }));
|
|
dispatch(bboxHeightChanged({ height, ...options }));
|
|
}
|
|
}, [controlImage, dispatch, optimalDimension, shift]);
|
|
|
|
const handleMouseEnter = useCallback(() => {
|
|
setIsMouseOverImage(true);
|
|
}, []);
|
|
|
|
const handleMouseLeave = useCallback(() => {
|
|
setIsMouseOverImage(false);
|
|
}, []);
|
|
|
|
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
|
if (controlImage) {
|
|
return {
|
|
id: controlAdapter.id,
|
|
payloadType: 'IMAGE_DTO',
|
|
payload: { imageDTO: controlImage },
|
|
};
|
|
}
|
|
}, [controlImage, controlAdapter.id]);
|
|
|
|
const shouldShowProcessedImage =
|
|
controlImage &&
|
|
processedControlImage &&
|
|
!isMouseOverImage &&
|
|
!controlAdapter.processorPendingBatchId &&
|
|
controlAdapter.processorConfig !== null;
|
|
|
|
useEffect(() => {
|
|
if (!isConnected) {
|
|
return;
|
|
}
|
|
if (isErrorControlImage) {
|
|
onErrorLoadingImage();
|
|
}
|
|
if (isErrorProcessedControlImage) {
|
|
onErrorLoadingProcessedImage();
|
|
}
|
|
}, [
|
|
handleResetControlImage,
|
|
isConnected,
|
|
isErrorControlImage,
|
|
isErrorProcessedControlImage,
|
|
onErrorLoadingImage,
|
|
onErrorLoadingProcessedImage,
|
|
]);
|
|
|
|
return (
|
|
<Flex
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
position="relative"
|
|
w={36}
|
|
h={36}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
>
|
|
<IAIDndImage
|
|
draggableData={draggableData}
|
|
droppableData={droppableData}
|
|
imageDTO={controlImage}
|
|
isDropDisabled={shouldShowProcessedImage}
|
|
postUploadAction={postUploadAction}
|
|
/>
|
|
|
|
<Box
|
|
position="absolute"
|
|
top={0}
|
|
insetInlineStart={0}
|
|
w="full"
|
|
h="full"
|
|
opacity={shouldShowProcessedImage ? 1 : 0}
|
|
transitionProperty="common"
|
|
transitionDuration="normal"
|
|
pointerEvents="none"
|
|
>
|
|
<IAIDndImage
|
|
draggableData={draggableData}
|
|
droppableData={droppableData}
|
|
imageDTO={processedControlImage}
|
|
isUploadDisabled={true}
|
|
onError={handleResetControlImage}
|
|
/>
|
|
</Box>
|
|
|
|
{controlImage && (
|
|
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
|
<IAIDndImageIcon
|
|
onClick={handleResetControlImage}
|
|
icon={<PiArrowCounterClockwiseBold size={16} />}
|
|
tooltip={t('controlnet.resetControlImage')}
|
|
/>
|
|
<IAIDndImageIcon
|
|
onClick={handleSaveControlImage}
|
|
icon={<PiFloppyDiskBold size={16} />}
|
|
tooltip={t('controlnet.saveControlImage')}
|
|
/>
|
|
<IAIDndImageIcon
|
|
onClick={handleSetControlImageToDimensions}
|
|
icon={<PiRulerBold size={16} />}
|
|
tooltip={
|
|
shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')
|
|
}
|
|
/>
|
|
</Flex>
|
|
)}
|
|
|
|
{controlAdapter.processorPendingBatchId !== null && (
|
|
<Flex
|
|
position="absolute"
|
|
top={0}
|
|
insetInlineStart={0}
|
|
w="full"
|
|
h="full"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
opacity={0.8}
|
|
borderRadius="base"
|
|
bg="base.900"
|
|
>
|
|
<Spinner size="xl" color="base.400" />
|
|
</Flex>
|
|
)}
|
|
</Flex>
|
|
);
|
|
}
|
|
);
|
|
|
|
ControlAdapterImagePreview.displayName = 'ControlAdapterImagePreview';
|