diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 049f9ea979..d127720bd5 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -380,7 +380,7 @@ "problemDeletingImagesDesc": "One or more images could not be deleted", "viewerImage": "Viewer Image", "compareImage": "Compare Image", - "selectForViewer": "Select for Viewer", + "openInViewer": "Open in Viewer", "selectForCompare": "Select for Compare", "selectAnImageToCompare": "Select an Image to Compare", "slider": "Slider", diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx index 1b9849ac6e..73316d1b1a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx @@ -7,6 +7,7 @@ import { imageToCompareChanged, } from 'features/gallery/store/gallerySlice'; import { memo, useCallback } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { PiArrowsOutBold, PiSwapBold, PiXBold } from 'react-icons/pi'; @@ -33,6 +34,7 @@ export const CompareToolbar = memo(() => { const exitCompare = useCallback(() => { dispatch(imageToCompareChanged(null)); }, [dispatch]); + useHotkeys('esc', exitCompare, [exitCompare]); return ( 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 ca740a5c16..5607d7dd4f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx @@ -1,21 +1,14 @@ -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import type { Dimensions } from 'features/canvas/store/canvasTypes'; +import { selectComparisonImages } from 'features/gallery/components/ImageViewer/common'; import { ImageComparisonHover } from 'features/gallery/components/ImageViewer/ImageComparisonHover'; import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide'; import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider'; -import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiImagesBold } from 'react-icons/pi'; -const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => { - const firstImage = gallerySlice.selection.slice(-1)[0] ?? null; - const secondImage = gallerySlice.imageToCompare; - return { firstImage, secondImage }; -}); - type Props = { containerDims: Dimensions; }; @@ -23,7 +16,7 @@ type Props = { export const ImageComparison = memo(({ containerDims }: Props) => { const { t } = useTranslation(); const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode); - const { firstImage, secondImage } = useAppSelector(selector); + const { firstImage, secondImage } = useAppSelector(selectComparisonImages); if (!firstImage || !secondImage) { // Should rarely/never happen - we don't render this component unless we have images to compare 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 9639daac10..3678c920c0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonDroppable.tsx @@ -1,17 +1,12 @@ 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 { CurrentImageDropData, SelectForCompareDropData } from 'features/dnd/types'; -import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => { - const firstImage = gallerySlice.selection.slice(-1)[0] ?? null; - const secondImage = gallerySlice.imageToCompare; - return { firstImage, secondImage }; -}); +import { selectComparisonImages } from './common'; const setCurrentImageDropData: CurrentImageDropData = { id: 'current-image', @@ -20,7 +15,8 @@ const setCurrentImageDropData: CurrentImageDropData = { export const ImageComparisonDroppable = memo(() => { const { t } = useTranslation(); - const { firstImage, secondImage } = useAppSelector(selector); + const imageViewer = useImageViewer(); + const { firstImage, secondImage } = useAppSelector(selectComparisonImages); const selectForCompareDropData = useMemo( () => ({ id: 'image-comparison', @@ -33,14 +29,17 @@ export const ImageComparisonDroppable = memo(() => { [firstImage?.image_name, secondImage?.image_name] ); + if (!imageViewer.isOpen) { + return ( + + + + ); + } + return ( - - - - - - + ); }); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx index 33a70f973c..530431fc4c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx @@ -1,43 +1,17 @@ import { Box, Flex } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar'; import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview'; import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison'; import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar'; -import type { InvokeTabName } from 'features/ui/store/tabMap'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { memo, useMemo } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; +import { memo } from 'react'; import { useMeasure } from 'react-use'; import { useImageViewer } from './useImageViewer'; -const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows']; - export const ImageViewer = memo(() => { - 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, workflowsMode, activeTabName]); + const imageViewer = useImageViewer(); const [containerRef, containerDims] = useMeasure(); - useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]); - useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]); - - if (!shouldShowViewer) { - return null; - } - return ( { alignItems="center" justifyContent="center" > - {isComparing && } - {!isComparing && } + {imageViewer.isComparing && } + {!imageViewer.isComparing && } - {!isComparing && } - {isComparing && } + {!imageViewer.isComparing && } + {imageViewer.isComparing && } ); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx index 3552c28a5b..7dc13afb48 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx @@ -9,33 +9,35 @@ import { PopoverTrigger, Text, } from '@invoke-ai/ui-library'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { PiCaretDownBold, PiCheckBold, PiEyeBold, PiPencilBold } from 'react-icons/pi'; -import { useImageViewer } from './useImageViewer'; - export const ViewerToggleMenu = () => { const { t } = useTranslation(); - const { isOpen, onClose, onOpen } = useImageViewer(); + const imageViewer = useImageViewer(); + useHotkeys('z', imageViewer.onToggle, [imageViewer]); + useHotkeys('esc', imageViewer.onClose, [imageViewer]); return ( - - + - -