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]