diff --git a/frontend/src/features/gallery/components/CurrentImagePreview.tsx b/frontend/src/features/gallery/components/CurrentImagePreview.tsx index d000a81e82..2f83b6c2d4 100644 --- a/frontend/src/features/gallery/components/CurrentImagePreview.tsx +++ b/frontend/src/features/gallery/components/CurrentImagePreview.tsx @@ -1,5 +1,5 @@ import { IconButton, Image } from '@chakra-ui/react'; -import { useState } from 'react'; +import { DragEvent, useState } from 'react'; import { FaAngleLeft, FaAngleRight } from 'react-icons/fa'; import { RootState, useAppDispatch, useAppSelector } from 'app/store'; import { @@ -12,13 +12,19 @@ import { createSelector } from '@reduxjs/toolkit'; import _ from 'lodash'; import { OptionsState, + setInitialImage, setIsLightBoxOpen, } from 'features/options/store/optionsSlice'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; +import { activeTabNameSelector } from 'features/options/store/optionsSelectors'; export const imagesSelector = createSelector( - [(state: RootState) => state.gallery, (state: RootState) => state.options], - (gallery: GalleryState, options: OptionsState) => { + [ + (state: RootState) => state.gallery, + (state: RootState) => state.options, + activeTabNameSelector, + ], + (gallery: GalleryState, options: OptionsState, activeTabName) => { const { currentCategory, currentImage, intermediateImage } = gallery; const { shouldShowImageDetails } = options; @@ -32,6 +38,7 @@ export const imagesSelector = createSelector( const imagesLength = tempImages.length; return { + activeTabName, imageToDisplay: intermediateImage ? intermediateImage : currentImage, isIntermediate: Boolean(intermediateImage), viewerImageToDisplay: currentImage, @@ -61,6 +68,7 @@ export default function CurrentImagePreview() { shouldShowImageDetails, imageToDisplay, isIntermediate, + activeTabName, } = useAppSelector(imagesSelector); const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = diff --git a/frontend/src/features/gallery/components/HoverableImage.tsx b/frontend/src/features/gallery/components/HoverableImage.tsx index c9765ab6fc..fff5b69259 100644 --- a/frontend/src/features/gallery/components/HoverableImage.tsx +++ b/frontend/src/features/gallery/components/HoverableImage.tsx @@ -13,7 +13,7 @@ import { } from 'features/gallery/store/gallerySlice'; import { FaCheck, FaTrashAlt } from 'react-icons/fa'; import DeleteImageModal from './DeleteImageModal'; -import { memo, useState } from 'react'; +import { DragEvent, memo, useState } from 'react'; import { setActiveTab, setAllImageToImageParameters, @@ -129,7 +129,6 @@ const HoverableImage = memo((props: HoverableImageProps) => { }; const handleUseInitialImage = async () => { - // check if the image exists before setting it as initial image if (metadata?.image?.init_image_path) { const response = await fetch(metadata.image.init_image_path); if (response.ok) { @@ -155,6 +154,11 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleSelectImage = () => dispatch(setCurrentImage(image)); + const handleDragStart = (e: DragEvent) => { + e.dataTransfer.setData('invokeai/imageUuid', uuid); + e.dataTransfer.effectAllowed = 'move'; + }; + return ( { @@ -169,6 +173,8 @@ const HoverableImage = memo((props: HoverableImageProps) => { onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} userSelect={'none'} + draggable={true} + onDragStart={handleDragStart} > ({ + resultImages: gallery.categories.result.images, + userImages: gallery.categories.user.images, +})); + +const useGetImageByUuid = () => { + const { resultImages, userImages } = useAppSelector(selector); + + return (uuid: string) => { + const resultImagesResult = resultImages.find( + (image) => image.uuid === uuid + ); + if (resultImagesResult) { + return resultImagesResult; + } + + const userImagesResult = userImages.find((image) => image.uuid === uuid); + if (userImagesResult) { + return userImagesResult; + } + }; +}; + +export default useGetImageByUuid; diff --git a/frontend/src/features/gallery/store/gallerySliceSelectors.ts b/frontend/src/features/gallery/store/gallerySliceSelectors.ts index 3485a0c027..a825a29c9c 100644 --- a/frontend/src/features/gallery/store/gallerySliceSelectors.ts +++ b/frontend/src/features/gallery/store/gallerySliceSelectors.ts @@ -95,3 +95,5 @@ export const hoverableImageSelector = createSelector( }, } ); + +export const gallerySelector = (state: RootState) => state.gallery; diff --git a/frontend/src/features/tabs/components/InvokeWorkarea.tsx b/frontend/src/features/tabs/components/InvokeWorkarea.tsx index ec6637cc61..aa21282669 100644 --- a/frontend/src/features/tabs/components/InvokeWorkarea.tsx +++ b/frontend/src/features/tabs/components/InvokeWorkarea.tsx @@ -1,16 +1,21 @@ import { Tooltip } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { ReactNode } from 'react'; +import { DragEvent, ReactNode } from 'react'; import { VscSplitHorizontal } from 'react-icons/vsc'; import { RootState, useAppDispatch, useAppSelector } from 'app/store'; import ImageGallery from 'features/gallery/components/ImageGallery'; import { activeTabNameSelector } from 'features/options/store/optionsSelectors'; import { OptionsState, + setInitialImage, setShowDualDisplay, } from 'features/options/store/optionsSlice'; -import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice'; +import { + setDoesCanvasNeedScaling, + setInitialCanvasImage, +} from 'features/canvas/store/canvasSlice'; import _ from 'lodash'; +import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid'; const workareaSelector = createSelector( [(state: RootState) => state.options, activeTabNameSelector], @@ -21,6 +26,7 @@ const workareaSelector = createSelector( shouldPinOptionsPanel, isLightBoxOpen, shouldShowDualDisplayButton: ['inpainting'].includes(activeTabName), + activeTabName, }; }, { @@ -39,14 +45,31 @@ type InvokeWorkareaProps = { const InvokeWorkarea = (props: InvokeWorkareaProps) => { const dispatch = useAppDispatch(); const { optionsPanel, children, styleClass } = props; - const { showDualDisplay, isLightBoxOpen, shouldShowDualDisplayButton } = - useAppSelector(workareaSelector); + const { + activeTabName, + showDualDisplay, + isLightBoxOpen, + shouldShowDualDisplayButton, + } = useAppSelector(workareaSelector); + + const getImageByUuid = useGetImageByUuid(); const handleDualDisplay = () => { dispatch(setShowDualDisplay(!showDualDisplay)); dispatch(setDoesCanvasNeedScaling(true)); }; + const handleDrop = (e: DragEvent) => { + const uuid = e.dataTransfer.getData('invokeai/imageUuid'); + const image = getImageByUuid(uuid); + if (!image) return; + if (activeTabName === 'img2img') { + dispatch(setInitialImage(image)); + } else if (activeTabName === 'unifiedCanvas') { + dispatch(setInitialCanvasImage(image)); + } + }; + return (
{ >
{optionsPanel} -
+
{children} {shouldShowDualDisplayButton && (