diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 6fd46aafcf..970c926500 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -386,7 +386,8 @@ "sideBySide": "Side-by-Side", "swapImages": "Swap Images", "compareOptions": "Comparison Options", - "sliderFitLabel": "Stretch second image to fit" + "sliderFitLabel": "Stretch second image to fit", + "exitCompare": "Exit Compare" }, "hotkeys": { "searchHotkeys": "Search Hotkeys", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts index 339b34d2be..6ca7ee7ffa 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts @@ -1,6 +1,6 @@ import { enqueueRequested } from 'app/store/actions'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { viewerModeChanged } from 'features/gallery/store/gallerySlice'; +import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { buildGenerationTabGraph } from 'features/nodes/util/graph/generation/buildGenerationTabGraph'; import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/generation/buildGenerationTabSDXLGraph'; @@ -34,7 +34,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) try { await req.unwrap(); if (shouldShowProgressInViewer) { - dispatch(viewerModeChanged('view')); + dispatch(isImageViewerOpenChanged(true)); } } finally { req.reset(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/galleryImageClicked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/galleryImageClicked.ts index 67c6d076ee..de04202435 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/galleryImageClicked.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/galleryImageClicked.ts @@ -1,7 +1,7 @@ import { createAction } from '@reduxjs/toolkit'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { selectionChanged } from 'features/gallery/store/gallerySlice'; +import { imageToCompareChanged, selectionChanged } from 'features/gallery/store/gallerySlice'; import { imagesApi } from 'services/api/endpoints/images'; import type { ImageDTO } from 'services/api/types'; import { imagesSelectors } from 'services/api/util'; @@ -11,6 +11,7 @@ export const galleryImageClicked = createAction<{ shiftKey: boolean; ctrlKey: boolean; metaKey: boolean; + altKey: boolean; }>('gallery/imageClicked'); /** @@ -28,7 +29,7 @@ export const addGalleryImageClickedListener = (startAppListening: AppStartListen startAppListening({ actionCreator: galleryImageClicked, effect: async (action, { dispatch, getState }) => { - const { imageDTO, shiftKey, ctrlKey, metaKey } = action.payload; + const { imageDTO, shiftKey, ctrlKey, metaKey, altKey } = action.payload; const state = getState(); const queryArgs = selectListImagesQueryArgs(state); const { data: listImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(state); @@ -41,7 +42,9 @@ export const addGalleryImageClickedListener = (startAppListening: AppStartListen const imageDTOs = imagesSelectors.selectAll(listImagesData); const selection = state.gallery.selection; - if (shiftKey) { + if (altKey) { + dispatch(imageToCompareChanged(imageDTO)); + } else if (shiftKey) { const rangeEndImageName = imageDTO.image_name; const lastSelectedImage = selection[selection.length - 1]?.image_name; const lastClickedIndex = imageDTOs.findIndex((n) => n.image_name === lastSelectedImage); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 2d8e10bae9..2841493ca6 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -7,7 +7,7 @@ import { boardIdSelected, galleryViewChanged, imageSelected, - viewerModeChanged, + isImageViewerOpenChanged, } from 'features/gallery/store/gallerySlice'; import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; @@ -108,7 +108,7 @@ export const addInvocationCompleteEventListener = (startAppListening: AppStartLi } dispatch(imageSelected(imageDTO)); - dispatch(viewerModeChanged('view')); + dispatch(isImageViewerOpenChanged(true)); } } } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index bc7e1bdb84..2b29ba9ddf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -46,11 +46,7 @@ type SingleSelectionMenuItemsProps = { const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const { imageDTO } = props; const optimalDimension = useAppSelector(selectOptimalDimension); - const maySelectForCompare = useAppSelector( - (s) => - s.gallery.imageToCompare?.image_name !== imageDTO.image_name && - s.gallery.selection.slice(-1)[0]?.image_name !== imageDTO.image_name - ); + const maySelectForCompare = useAppSelector((s) => s.gallery.imageToCompare?.image_name !== imageDTO.image_name); const dispatch = useAppDispatch(); const { t } = useTranslation(); const isCanvasEnabled = useFeatureStatus('canvas'); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index 812a042c8b..e5e216c97c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -11,7 +11,7 @@ import type { GallerySelectionDraggableData, ImageDraggableData, TypesafeDraggab import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; import { useMultiselect } from 'features/gallery/hooks/useMultiselect'; import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView'; -import { viewerModeChanged } from 'features/gallery/store/gallerySlice'; +import { imageToCompareChanged, isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import type { MouseEvent } from 'react'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -46,9 +46,7 @@ const GalleryImage = (props: HoverableImageProps) => { const { t } = useTranslation(); const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge); - const isSelectedForCompare = useAppSelector( - (s) => s.gallery.imageToCompare?.image_name === imageName && s.gallery.viewerMode === 'compare' - ); + const isSelectedForCompare = useAppSelector((s) => s.gallery.imageToCompare?.image_name === imageName); const { handleClick, isSelected, areMultiplesSelected } = useMultiselect(imageDTO); const customStarUi = useStore($customStarUI); @@ -107,7 +105,8 @@ const GalleryImage = (props: HoverableImageProps) => { }, []); const onDoubleClick = useCallback(() => { - dispatch(viewerModeChanged('view')); + dispatch(isImageViewerOpenChanged(true)); + dispatch(imageToCompareChanged(null)); }, [dispatch]); const handleMouseOut = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonToolbarButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonToolbarButtons.tsx index e9650445b5..2ee25d75a8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonToolbarButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonToolbarButtons.tsx @@ -12,7 +12,12 @@ import { Switch, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { comparedImagesSwapped, comparisonModeChanged, sliderFitChanged } from 'features/gallery/store/gallerySlice'; +import { + comparedImagesSwapped, + comparisonModeChanged, + imageToCompareChanged, + sliderFitChanged, +} from 'features/gallery/store/gallerySlice'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,20 +43,12 @@ export const ImageComparisonToolbarButtons = memo(() => { }, [dispatch] ); + const exitCompare = useCallback(() => { + dispatch(imageToCompareChanged(null)); + }, [dispatch]); return ( <> - - - - { - + + + + + {t('gallery.sliderFitLabel')} - + @@ -77,6 +86,7 @@ export const ImageComparisonToolbarButtons = memo(() => { + ); }); 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 32e9d606fc..f676a89f7e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx @@ -17,18 +17,19 @@ import { ViewerToggleMenu } from './ViewerToggleMenu'; const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows']; export const ImageViewer = memo(() => { - const { viewerMode, onToggle, openEditor } = useImageViewer(); + const { isOpen, onToggle, onClose } = useImageViewer(); const activeTabName = useAppSelector(activeTabNameSelector); + const isComparing = useAppSelector((s) => s.gallery.imageToCompare !== null); const isViewerEnabled = useMemo(() => VIEWER_ENABLED_TABS.includes(activeTabName), [activeTabName]); const shouldShowViewer = useMemo(() => { if (!isViewerEnabled) { return false; } - return viewerMode === 'view' || viewerMode === 'compare'; - }, [viewerMode, isViewerEnabled]); + return isOpen; + }, [isOpen, isViewerEnabled]); useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]); - useHotkeys('esc', openEditor, { enabled: isViewerEnabled }, [isViewerEnabled, openEditor]); + useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]); if (!shouldShowViewer) { return null; @@ -58,8 +59,8 @@ export const ImageViewer = memo(() => { - {viewerMode === 'view' && } - {viewerMode === 'compare' && } + {!isComparing && } + {isComparing && } @@ -68,8 +69,8 @@ export const ImageViewer = memo(() => { - {viewerMode === 'view' && } - {viewerMode === 'compare' && } + {!isComparing && } + {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 f5b02db2fc..3552c28a5b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx @@ -8,60 +8,23 @@ import { PopoverContent, PopoverTrigger, Text, - useDisclosure, } from '@invoke-ai/ui-library'; -import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiCaretDownBold, PiCheckBold, PiEyeBold, PiImagesBold, PiPencilBold } from 'react-icons/pi'; +import { PiCaretDownBold, PiCheckBold, PiEyeBold, PiPencilBold } from 'react-icons/pi'; import { useImageViewer } from './useImageViewer'; export const ViewerToggleMenu = () => { const { t } = useTranslation(); - const { isOpen, onOpen, onClose } = useDisclosure(); - const { viewerMode, openEditor, openViewer, openCompare } = useImageViewer(); - const icon = useMemo(() => { - if (viewerMode === 'view') { - return ; - } - if (viewerMode === 'edit') { - return ; - } - if (viewerMode === 'compare') { - return ; - } - }, [viewerMode]); - const label = useMemo(() => { - if (viewerMode === 'view') { - return t('common.viewing'); - } - if (viewerMode === 'edit') { - return t('common.editing'); - } - if (viewerMode === 'compare') { - return t('common.comparing'); - } - }, [t, viewerMode]); - const _openEditor = useCallback(() => { - openEditor(); - onClose(); - }, [onClose, openEditor]); - const _openViewer = useCallback(() => { - openViewer(); - onClose(); - }, [onClose, openViewer]); - const _openCompare = useCallback(() => { - openCompare(); - onClose(); - }, [onClose, openCompare]); + const { isOpen, onClose, onOpen } = useImageViewer(); return ( - + @@ -70,9 +33,9 @@ export const ViewerToggleMenu = () => { - - - diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx index fe4dc47607..57b3697b7e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx @@ -1,26 +1,22 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { viewerModeChanged } from 'features/gallery/store/gallerySlice'; +import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import { useCallback } from 'react'; export const useImageViewer = () => { const dispatch = useAppDispatch(); - const viewerMode = useAppSelector((s) => s.gallery.viewerMode); + const isOpen = useAppSelector((s) => s.gallery.isImageViewerOpen); - const openEditor = useCallback(() => { - dispatch(viewerModeChanged('edit')); + const onClose = useCallback(() => { + dispatch(isImageViewerOpenChanged(false)); }, [dispatch]); - const openViewer = useCallback(() => { - dispatch(viewerModeChanged('view')); + const onOpen = useCallback(() => { + dispatch(isImageViewerOpenChanged(true)); }, [dispatch]); const onToggle = useCallback(() => { - dispatch(viewerModeChanged(viewerMode === 'view' ? 'edit' : 'view')); - }, [dispatch, viewerMode]); + dispatch(isImageViewerOpenChanged(!isOpen)); + }, [dispatch, isOpen]); - const openCompare = useCallback(() => { - dispatch(viewerModeChanged('compare')); - }, [dispatch]); - - return { viewerMode, openEditor, openViewer, openCompare, onToggle }; + return { isOpen, onOpen, onClose, onToggle }; }; diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryHotkeys.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryHotkeys.ts index 1efc317e3a..931d93272b 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryHotkeys.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryHotkeys.ts @@ -27,16 +27,16 @@ export const useGalleryHotkeys = () => { useGalleryNavigation(); useHotkeys( - 'left', - () => { - canNavigateGallery && handleLeftImage(); + ['left', 'alt+left'], + (e) => { + canNavigateGallery && handleLeftImage(e.altKey); }, [handleLeftImage, canNavigateGallery] ); useHotkeys( - 'right', - () => { + ['right', 'alt+right'], + (e) => { if (!canNavigateGallery) { return; } @@ -45,29 +45,29 @@ export const useGalleryHotkeys = () => { return; } if (!isOnLastImage) { - handleRightImage(); + handleRightImage(e.altKey); } }, [isOnLastImage, areMoreImagesAvailable, handleLoadMoreImages, isFetching, handleRightImage, canNavigateGallery] ); useHotkeys( - 'up', - () => { - handleUpImage(); + ['up', 'alt+up'], + (e) => { + handleUpImage(e.altKey); }, { preventDefault: true }, [handleUpImage] ); useHotkeys( - 'down', - () => { + ['down', 'alt+down'], + (e) => { if (!areImagesBelowCurrent && areMoreImagesAvailable && !isFetching) { handleLoadMoreImages(); return; } - handleDownImage(); + handleDownImage(e.altKey); }, { preventDefault: true }, [areImagesBelowCurrent, areMoreImagesAvailable, handleLoadMoreImages, isFetching, handleDownImage] diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts index 1464c23285..177d7c7318 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts @@ -1,11 +1,11 @@ +import { useAltModifier } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; import { imageItemContainerTestId } from 'features/gallery/components/ImageGrid/ImageGridItemContainer'; import { imageListContainerTestId } from 'features/gallery/components/ImageGrid/ImageGridListContainer'; import { virtuosoGridRefs } from 'features/gallery/components/ImageGrid/types'; import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages'; -import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice'; import { getIsVisible } from 'features/gallery/util/getIsVisible'; import { getScrollToIndexAlign } from 'features/gallery/util/getScrollToIndexAlign'; import { clamp } from 'lodash-es'; @@ -106,10 +106,10 @@ const getImageFuncs = { }; type UseGalleryNavigationReturn = { - handleLeftImage: () => void; - handleRightImage: () => void; - handleUpImage: () => void; - handleDownImage: () => void; + handleLeftImage: (alt?: boolean) => void; + handleRightImage: (alt?: boolean) => void; + handleUpImage: (alt?: boolean) => void; + handleDownImage: (alt?: boolean) => void; isOnFirstImage: boolean; isOnLastImage: boolean; areImagesBelowCurrent: boolean; @@ -123,7 +123,15 @@ type UseGalleryNavigationReturn = { */ export const useGalleryNavigation = (): UseGalleryNavigationReturn => { const dispatch = useAppDispatch(); - const lastSelectedImage = useAppSelector(selectLastSelectedImage); + const alt = useAltModifier(); + const lastSelectedImage = useAppSelector((s) => { + const lastSelected = s.gallery.selection.slice(-1)[0] ?? null; + if (alt) { + return s.gallery.imageToCompare ?? lastSelected; + } else { + return lastSelected; + } + }); const { queryResult: { data }, } = useGalleryImages(); @@ -136,7 +144,7 @@ export const useGalleryNavigation = (): UseGalleryNavigationReturn => { }, [lastSelectedImage, data]); const handleNavigation = useCallback( - (direction: 'left' | 'right' | 'up' | 'down') => { + (direction: 'left' | 'right' | 'up' | 'down', alt?: boolean) => { if (!data) { return; } @@ -144,10 +152,14 @@ export const useGalleryNavigation = (): UseGalleryNavigationReturn => { if (!image || index === lastSelectedImageIndex) { return; } - dispatch(imageSelected(image)); + if (alt) { + dispatch(imageToCompareChanged(image)); + } else { + dispatch(imageSelected(image)); + } scrollToImage(image.image_name, index); }, - [dispatch, lastSelectedImageIndex, data] + [data, lastSelectedImageIndex, dispatch] ); const isOnFirstImage = useMemo(() => lastSelectedImageIndex === 0, [lastSelectedImageIndex]); @@ -162,21 +174,33 @@ export const useGalleryNavigation = (): UseGalleryNavigationReturn => { return lastSelectedImageIndex + imagesPerRow < loadedImagesCount; }, [lastSelectedImageIndex, loadedImagesCount]); - const handleLeftImage = useCallback(() => { - handleNavigation('left'); - }, [handleNavigation]); + const handleLeftImage = useCallback( + (alt?: boolean) => { + handleNavigation('left', alt); + }, + [handleNavigation] + ); - const handleRightImage = useCallback(() => { - handleNavigation('right'); - }, [handleNavigation]); + const handleRightImage = useCallback( + (alt?: boolean) => { + handleNavigation('right', alt); + }, + [handleNavigation] + ); - const handleUpImage = useCallback(() => { - handleNavigation('up'); - }, [handleNavigation]); + const handleUpImage = useCallback( + (alt?: boolean) => { + handleNavigation('up', alt); + }, + [handleNavigation] + ); - const handleDownImage = useCallback(() => { - handleNavigation('down'); - }, [handleNavigation]); + const handleDownImage = useCallback( + (alt?: boolean) => { + handleNavigation('down', alt); + }, + [handleNavigation] + ); return { handleLeftImage, diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts b/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts index f84a349d2a..5f7c5e4da8 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts @@ -36,6 +36,7 @@ export const useMultiselect = (imageDTO?: ImageDTO) => { shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, + altKey: e.altKey, }) ); }, diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 4a49acafc5..7861515eb5 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -6,7 +6,7 @@ import { boardsApi } from 'services/api/endpoints/boards'; import { imagesApi } from 'services/api/endpoints/images'; import type { ImageDTO } from 'services/api/types'; -import type { BoardId, ComparisonMode, GalleryState, GalleryView, ViewerMode } from './types'; +import type { BoardId, ComparisonMode, GalleryState, GalleryView } from './types'; import { IMAGE_LIMIT, INITIAL_IMAGE_LIMIT } from './types'; const initialGalleryState: GalleryState = { @@ -21,7 +21,7 @@ const initialGalleryState: GalleryState = { boardSearchText: '', limit: INITIAL_IMAGE_LIMIT, offset: 0, - viewerMode: 'view', + isImageViewerOpen: true, imageToCompare: null, comparisonMode: 'slider', sliderFit: 'fill', @@ -40,7 +40,7 @@ export const gallerySlice = createSlice({ imageToCompareChanged: (state, action: PayloadAction) => { state.imageToCompare = action.payload; if (action.payload) { - state.viewerMode = 'compare'; + state.isImageViewerOpen = true; } }, comparisonModeChanged: (state, action: PayloadAction) => { @@ -88,8 +88,12 @@ export const gallerySlice = createSlice({ alwaysShowImageSizeBadgeChanged: (state, action: PayloadAction) => { state.alwaysShowImageSizeBadge = action.payload; }, - viewerModeChanged: (state, action: PayloadAction) => { - state.viewerMode = action.payload; + isImageViewerOpenChanged: (state, action: PayloadAction) => { + if (state.isImageViewerOpen && state.imageToCompare) { + state.imageToCompare = null; + return; + } + state.isImageViewerOpen = action.payload; }, comparedImagesSwapped: (state) => { if (state.imageToCompare) { @@ -138,7 +142,7 @@ export const { boardSearchTextChanged, moreImagesLoaded, alwaysShowImageSizeBadgeChanged, - viewerModeChanged, + isImageViewerOpenChanged, imageToCompareChanged, comparisonModeChanged, comparedImagesSwapped, @@ -164,5 +168,13 @@ export const galleryPersistConfig: PersistConfig = { name: gallerySlice.name, initialState: initialGalleryState, migrate: migrateGalleryState, - persistDenylist: ['selection', 'selectedBoardId', 'galleryView', 'offset', 'limit', 'viewerMode', 'imageToCompare'], + persistDenylist: [ + 'selection', + 'selectedBoardId', + 'galleryView', + 'offset', + 'limit', + 'isImageViewerOpen', + 'imageToCompare', + ], }; diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts index 1388c792c3..1bdc91fc1e 100644 --- a/invokeai/frontend/web/src/features/gallery/store/types.ts +++ b/invokeai/frontend/web/src/features/gallery/store/types.ts @@ -8,7 +8,6 @@ export const IMAGE_LIMIT = 20; export type GalleryView = 'images' | 'assets'; export type BoardId = 'none' | (string & Record); export type ComparisonMode = 'slider' | 'side-by-side'; -export type ViewerMode = 'edit' | 'view' | 'compare'; export type GalleryState = { selection: ImageDTO[]; @@ -25,5 +24,5 @@ export type GalleryState = { imageToCompare: ImageDTO | null; comparisonMode: ComparisonMode; sliderFit: 'contain' | 'fill'; - viewerMode: ViewerMode; + isImageViewerOpen: boolean; }; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx index 23a7837b20..b78d5dce9a 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -3,7 +3,7 @@ import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/u import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent'; -import { viewerModeChanged } from 'features/gallery/store/gallerySlice'; +import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls'; import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; @@ -51,7 +51,7 @@ const ParametersPanelTextToImage = () => { const onChangeTabs = useCallback( (i: number) => { if (i === 1) { - dispatch(viewerModeChanged('edit')); + dispatch(isImageViewerOpenChanged(false)); } }, [dispatch]