From ff2b2fad83a548cf20aa84709e23fd40174333fb Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Sat, 1 Jun 2024 11:43:49 +1000
Subject: [PATCH] feat(ui): revise drop zones
The main viewer area has two drop zones:
- Select for Viewer
- Select for Compare
These do what you'd imagine they would do.
---
invokeai/frontend/web/public/locales/en.json | 1 +
.../listeners/imageDropped.ts | 4 +-
.../web/src/features/dnd/types/index.ts | 2 +-
.../web/src/features/dnd/util/isValidDrop.ts | 6 +--
.../ImageViewer/CurrentImagePreview.tsx | 3 --
.../ImageViewer/ImageComparison.tsx | 53 ++-----------------
.../ImageViewer/ImageComparisonDroppable.tsx | 21 ++++++--
.../ImageViewer/ImageComparisonSlider.tsx | 8 +--
.../components/ImageViewer/ImageViewer.tsx | 7 ++-
.../ImageViewer/ImageViewerWorkflows.tsx | 45 ----------------
.../components/ImageViewer/ViewerToolbar.tsx | 15 +++++-
.../features/ui/components/tabs/NodesTab.tsx | 6 ++-
.../ui/components/tabs/TextToImageTab.tsx | 2 +
13 files changed, 58 insertions(+), 115 deletions(-)
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerWorkflows.tsx
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 351e483fcc..9a10cde6ce 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -380,6 +380,7 @@
"problemDeletingImagesDesc": "One or more images could not be deleted",
"viewerImage": "Viewer Image",
"compareImage": "Compare Image",
+ "selectForViewer": "Select for Viewer",
"selectForCompare": "Select for Compare",
"selectAnImageToCompare": "Select an Image to Compare",
"slider": "Slider",
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
index 84823407e9..7cb0703af8 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
@@ -15,7 +15,7 @@ import {
} from 'features/controlLayers/store/controlLayersSlice';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
-import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
+import { imageSelected, imageToCompareChanged, isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
@@ -54,6 +54,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO
) {
dispatch(imageSelected(activeData.payload.imageDTO));
+ dispatch(isImageViewerOpenChanged(true));
return;
}
@@ -195,6 +196,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
) {
const { imageDTO } = activeData.payload;
dispatch(imageToCompareChanged(imageDTO));
+ dispatch(isImageViewerOpenChanged(true));
return;
}
diff --git a/invokeai/frontend/web/src/features/dnd/types/index.ts b/invokeai/frontend/web/src/features/dnd/types/index.ts
index f66fec0ea1..6fcf18421e 100644
--- a/invokeai/frontend/web/src/features/dnd/types/index.ts
+++ b/invokeai/frontend/web/src/features/dnd/types/index.ts
@@ -18,7 +18,7 @@ type BaseDropData = {
id: string;
};
-type CurrentImageDropData = BaseDropData & {
+export type CurrentImageDropData = BaseDropData & {
actionType: 'SET_CURRENT_IMAGE';
};
diff --git a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
index d8e9d98e10..6dec862345 100644
--- a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
+++ b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
@@ -30,11 +30,7 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
case 'SET_NODES_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SELECT_FOR_COMPARE':
- return (
- payloadType === 'IMAGE_DTO' &&
- activeData.id !== 'image-compare-first-image' &&
- activeData.id !== 'image-compare-second-image'
- );
+ return payloadType === 'IMAGE_DTO';
case 'ADD_TO_BOARD': {
// If the board is the same, don't allow the drop
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
index 5de4f28d2a..a812391992 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
@@ -6,7 +6,6 @@ import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import type { TypesafeDraggableData } from 'features/dnd/types';
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
-import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import type { AnimationProps } from 'framer-motion';
@@ -75,12 +74,10 @@ const CurrentImagePreview = () => {
isUploadDisabled={true}
fitContainer
useThumbailFallback
- dropLabel={t('gallery.setCurrentImage')}
noContentFallback={}
dataTestId="image-preview"
/>
)}
-
{shouldShowImageDetails && imageDTO && (
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
index 74acdfa13f..a0dc48bd5d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
@@ -1,16 +1,12 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
-import IAIDroppable from 'common/components/IAIDroppable';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
-import type { SelectForCompareDropData } from 'features/dnd/types';
import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide';
import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
-import type { PropsWithChildren } from 'react';
-import { memo, useMemo } from 'react';
+import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiImagesBold } from 'react-icons/pi';
-import type { ImageDTO } from 'services/api/types';
const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => {
const firstImage = gallerySlice.selection.slice(-1)[0] ?? null;
@@ -24,56 +20,17 @@ export const ImageComparison = memo(() => {
const { firstImage, secondImage } = useAppSelector(selector);
if (!firstImage || !secondImage) {
- return (
-
-
-
- );
+ // Should rarely/never happen - we don't render this component unless we have images to compare
+ return ;
}
if (comparisonMode === 'slider') {
- return (
-
-
-
- );
+ return ;
}
if (comparisonMode === 'side-by-side') {
- return (
-
-
-
- );
+ return ;
}
});
ImageComparison.displayName = 'ImageComparison';
-
-type Props = PropsWithChildren<{
- firstImage: ImageDTO | null;
- secondImage: ImageDTO | null;
-}>;
-
-const ImageComparisonWrapper = memo((props: Props) => {
- const droppableData = useMemo(
- () => ({
- id: 'image-comparison',
- actionType: 'SELECT_FOR_COMPARE',
- context: {
- firstImageName: props.firstImage?.image_name,
- secondImageName: props.secondImage?.image_name,
- },
- }),
- [props.firstImage?.image_name, props.secondImage?.image_name]
- );
-
- return (
- <>
- {props.children}
-
- >
- );
-});
-
-ImageComparisonWrapper.displayName = 'ImageComparisonWrapper';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx
index 6f163f63cf..9639daac10 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx
@@ -1,7 +1,8 @@
+import { Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import IAIDroppable from 'common/components/IAIDroppable';
-import type { SelectForCompareDropData } from 'features/dnd/types';
+import type { CurrentImageDropData, SelectForCompareDropData } from 'features/dnd/types';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -12,10 +13,15 @@ const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => {
return { firstImage, secondImage };
});
+const setCurrentImageDropData: CurrentImageDropData = {
+ id: 'current-image',
+ actionType: 'SET_CURRENT_IMAGE',
+};
+
export const ImageComparisonDroppable = memo(() => {
const { t } = useTranslation();
const { firstImage, secondImage } = useAppSelector(selector);
- const droppableData = useMemo(
+ const selectForCompareDropData = useMemo(
() => ({
id: 'image-comparison',
actionType: 'SELECT_FOR_COMPARE',
@@ -27,7 +33,16 @@ export const ImageComparisonDroppable = memo(() => {
[firstImage?.image_name, secondImage?.image_name]
);
- return ;
+ return (
+
+
+
+
+
+
+
+
+ );
});
ImageComparisonDroppable.displayName = 'ImageComparisonDroppable';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
index 39cb1d43ae..3ced364b64 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
@@ -132,7 +132,7 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
justifyContent="center"
>
{
const { isOpen, onToggle, onClose } = useImageViewer();
const activeTabName = useAppSelector(activeTabNameSelector);
+ const workflowsMode = useAppSelector((s) => s.workflow.mode);
const isComparing = useAppSelector((s) => s.gallery.imageToCompare !== null);
const isViewerEnabled = useMemo(() => VIEWER_ENABLED_TABS.includes(activeTabName), [activeTabName]);
const shouldShowViewer = useMemo(() => {
+ if (activeTabName === 'workflows' && workflowsMode === 'view') {
+ return true;
+ }
if (!isViewerEnabled) {
return false;
}
return isOpen;
- }, [isOpen, isViewerEnabled]);
+ }, [isOpen, isViewerEnabled, workflowsMode, activeTabName]);
useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]);
useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]);
@@ -45,7 +49,6 @@ export const ImageViewer = memo(() => {
rowGap={4}
alignItems="center"
justifyContent="center"
- zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
>
{isComparing && }
{!isComparing && }
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerWorkflows.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerWorkflows.tsx
deleted file mode 100644
index fe09f11be6..0000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerWorkflows.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Flex } from '@invoke-ai/ui-library';
-import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
-import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
-import { memo } from 'react';
-
-import CurrentImageButtons from './CurrentImageButtons';
-import CurrentImagePreview from './CurrentImagePreview';
-
-export const ImageViewerWorkflows = memo(() => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
-
-ImageViewerWorkflows.displayName = 'ImageViewerWorkflows';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
index 6310874030..21d3ba59d4 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
@@ -1,12 +1,23 @@
import { Flex } from '@invoke-ai/ui-library';
+import { useAppSelector } from 'app/store/storeHooks';
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
-import { memo } from 'react';
+import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
+import { memo, useMemo } from 'react';
import CurrentImageButtons from './CurrentImageButtons';
import { ViewerToggleMenu } from './ViewerToggleMenu';
export const ViewerToolbar = memo(() => {
+ const workflowsMode = useAppSelector((s) => s.workflow.mode);
+ const activeTabName = useAppSelector(activeTabNameSelector);
+ const shouldShowToggleMenu = useMemo(() => {
+ if (activeTabName !== 'workflows') {
+ return true;
+ }
+ return workflowsMode === 'edit';
+ }, [workflowsMode, activeTabName]);
+
return (
@@ -20,7 +31,7 @@ export const ViewerToolbar = memo(() => {
-
+ {shouldShowToggleMenu && }
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/NodesTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/NodesTab.tsx
index b4f473ae03..256a4331cd 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/NodesTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/NodesTab.tsx
@@ -1,6 +1,7 @@
import { Box } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
-import { ImageViewerWorkflows } from 'features/gallery/components/ImageViewer/ImageViewerWorkflows';
+import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
+import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditor from 'features/nodes/components/NodeEditor';
import { memo } from 'react';
import { ReactFlowProvider } from 'reactflow';
@@ -10,7 +11,8 @@ const NodesTab = () => {
if (mode === 'view') {
return (
-
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
index 1c1c9c24a4..5583624823 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
@@ -1,5 +1,6 @@
import { Box } from '@invoke-ai/ui-library';
import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor';
+import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import { memo } from 'react';
@@ -8,6 +9,7 @@ const TextToImageTab = () => {
+
);
};