From 241844bdefc2c04dcec63c5a34d486cc45440c46 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:27:14 +1000 Subject: [PATCH] refactor(ui): rip out image viewer as modal --- .../src/app/components/GlobalHookIsolator.tsx | 8 -- .../app/components/GlobalModalIsolator.tsx | 2 - .../web/src/app/hooks/useStudioInitAction.ts | 4 - .../web/src/features/dnd/DndImage.tsx | 4 - .../src/features/dnd/FullscreenDropzone.tsx | 13 +-- ...ImageMenuItemNewCanvasFromImageSubMenu.tsx | 14 +-- .../ImageMenuItemNewLayerFromImageSubMenu.tsx | 17 +-- .../ImageMenuItemOpenInViewer.tsx | 7 +- .../ImageMenuItemUseAsRefImage.tsx | 5 +- .../components/ImageGrid/GalleryImage.tsx | 4 - .../GalleryImageOpenInViewerIconButton.tsx | 7 +- .../components/ImageViewer/ImageViewer.tsx | 105 +----------------- .../components/ImageViewer/ImageViewer2.tsx | 85 +------------- .../components/ImageViewer/useImageViewer.ts | 62 ----------- 14 files changed, 20 insertions(+), 317 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts diff --git a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx index 17bb4f4ce4..f9644d96ac 100644 --- a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx +++ b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx @@ -11,11 +11,9 @@ import { useFocusRegionWatcher } from 'common/hooks/focus'; import { useCloseChakraTooltipsOnDragFix } from 'common/hooks/useCloseChakraTooltipsOnDragFix'; import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; import { useDynamicPromptsWatcher } from 'features/dynamicPrompts/hooks/useDynamicPromptsWatcher'; -import { toggleImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast'; import { useWorkflowBuilderWatcher } from 'features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher'; import { useReadinessWatcher } from 'features/queue/store/readiness'; -import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; import { configChanged } from 'features/system/store/configSlice'; import { selectLanguage } from 'features/system/store/systemSelectors'; import i18n from 'i18n'; @@ -72,12 +70,6 @@ export const GlobalHookIsolator = memo( useWorkflowBuilderWatcher(); useDynamicPromptsWatcher(); - useRegisteredHotkeys({ - id: 'toggleViewer', - category: 'viewer', - callback: toggleImageViewer, - }); - return null; } ); diff --git a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx index b7bacf3d2c..662994ec41 100644 --- a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx +++ b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx @@ -11,7 +11,6 @@ import { FullscreenDropzone } from 'features/dnd/FullscreenDropzone'; import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; import { ImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; -import { ImageViewerModal } from 'features/gallery/components/ImageViewer/ImageViewer'; import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal'; import { WorkflowLibraryModal } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal'; import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog'; @@ -61,7 +60,6 @@ export const GlobalModalIsolator = memo(() => { - ); }); diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts index b08d40417a..7dfaae2759 100644 --- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts +++ b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts @@ -7,7 +7,6 @@ import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice'; import { paramsReset } from 'features/controlLayers/store/paramsSlice'; import type { CanvasRasterLayerState } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/util'; -import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { sentImageToCanvas } from 'features/gallery/store/actions'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { $hasTemplates } from 'features/nodes/store/nodesSlice'; @@ -94,7 +93,6 @@ export const useStudioInitAction = (action?: StudioInitAction) => { store.dispatch(canvasReset()); store.dispatch(rasterLayerAdded({ overrides, isSelected: true })); store.dispatch(sentImageToCanvas()); - $imageViewer.set(false); toast({ title: t('toast.sentToCanvas'), status: 'info', @@ -164,12 +162,10 @@ export const useStudioInitAction = (action?: StudioInitAction) => { // Go to the canvas tab, open the image viewer, and enable send-to-gallery mode store.dispatch(paramsReset()); store.dispatch(activeTabCanvasRightPanelChanged('gallery')); - $imageViewer.set(true); break; case 'canvas': // Go to the canvas tab, close the image viewer, and disable send-to-gallery mode store.dispatch(canvasReset()); - $imageViewer.set(false); break; case 'workflows': // Go to the workflows tab diff --git a/invokeai/frontend/web/src/features/dnd/DndImage.tsx b/invokeai/frontend/web/src/features/dnd/DndImage.tsx index 9aed1798b4..01ac203653 100644 --- a/invokeai/frontend/web/src/features/dnd/DndImage.tsx +++ b/invokeai/frontend/web/src/features/dnd/DndImage.tsx @@ -8,7 +8,6 @@ import type { DndDragPreviewSingleImageState } from 'features/dnd/DndDragPreview import { createSingleImageDragPreview, setSingleImageDragPreview } from 'features/dnd/DndDragPreviewSingleImage'; import { firefoxDndFix } from 'features/dnd/util'; import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; -import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react'; import type { ImageDTO } from 'services/api/types'; @@ -48,9 +47,6 @@ export const DndImage = memo( getInitialData: () => singleImageDndSource.getData({ imageDTO }, imageDTO.image_name), onDragStart: () => { setIsDragging(true); - if ($imageViewer.get()) { - $imageViewer.set(false); - } }, onDrop: () => { setIsDragging(false); diff --git a/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx b/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx index dba20d4e32..84537b7d16 100644 --- a/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx +++ b/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx @@ -4,7 +4,6 @@ import { containsFiles, getFiles } from '@atlaskit/pragmatic-drag-and-drop/exter import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled'; import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { Box, Flex, Heading } from '@invoke-ai/ui-library'; -import { useStore } from '@nanostores/react'; import { getStore } from 'app/store/nanostores/store'; import { useAppSelector } from 'app/store/storeHooks'; import { $focusedRegion } from 'common/hooks/focus'; @@ -12,7 +11,6 @@ import { useClientSideUpload } from 'common/hooks/useClientSideUpload'; import { setFileToPaste } from 'features/controlLayers/components/CanvasPasteModal'; import { DndDropOverlay } from 'features/dnd/DndDropOverlay'; import type { DndTargetState } from 'features/dnd/types'; -import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors'; import { selectIsClientSideUploadEnabled } from 'features/system/store/configSlice'; import { toast } from 'features/toast/toast'; @@ -70,7 +68,6 @@ export const FullscreenDropzone = memo(() => { const ref = useRef(null); const [dndState, setDndState] = useState('idle'); const activeTab = useAppSelector(selectActiveTab); - const isImageViewerOpen = useStore($imageViewer); const isClientSideUploadEnabled = useAppSelector(selectIsClientSideUploadEnabled); const clientSideUpload = useClientSideUpload(); @@ -96,13 +93,7 @@ export const FullscreenDropzone = memo(() => { // While on the canvas tab and when pasting a single image, canvas may want to create a new layer. Let it handle // the paste event. const [firstImageFile] = files; - if ( - focusedRegion === 'canvas' && - !isImageViewerOpen && - activeTab === 'canvas' && - files.length === 1 && - firstImageFile - ) { + if (focusedRegion === 'canvas' && activeTab === 'canvas' && files.length === 1 && firstImageFile) { setFileToPaste(firstImageFile); return; } @@ -125,7 +116,7 @@ export const FullscreenDropzone = memo(() => { uploadImages(uploadArgs); } }, - [activeTab, isImageViewerOpen, t, isClientSideUploadEnabled, clientSideUpload] + [activeTab, t, isClientSideUploadEnabled, clientSideUpload] ); const onPaste = useCallback( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx index d2ce3a3cfc..405585546f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx @@ -2,7 +2,6 @@ import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppStore } from 'app/store/nanostores/store'; import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu'; import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy'; -import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { newCanvasFromImage } from 'features/imageActions/actions'; import { toast } from 'features/toast/toast'; @@ -16,56 +15,51 @@ export const ImageMenuItemNewCanvasFromImageSubMenu = memo(() => { const subMenu = useSubMenu(); const store = useAppStore(); const imageDTO = useImageDTOContext(); - const imageViewer = useImageViewer(); const isBusy = useCanvasIsBusySafe(); const onClickNewCanvasWithRasterLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; await newCanvasFromImage({ imageDTO, withResize: false, type: 'raster_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewCanvasWithControlLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; await newCanvasFromImage({ imageDTO, withResize: false, type: 'control_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewCanvasWithRasterLayerFromImageWithResize = useCallback(async () => { const { dispatch, getState } = store; await newCanvasFromImage({ imageDTO, withResize: true, type: 'raster_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewCanvasWithControlLayerFromImageWithResize = useCallback(async () => { const { dispatch, getState } = store; await newCanvasFromImage({ imageDTO, withResize: true, type: 'control_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); return ( }> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx index 4fc9934f07..0f751bde33 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx @@ -3,7 +3,6 @@ import { useAppStore } from 'app/store/nanostores/store'; import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu'; import { NewLayerIcon } from 'features/controlLayers/components/common/icons'; import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy'; -import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { sentImageToCanvas } from 'features/gallery/store/actions'; import { createNewCanvasEntityFromImage } from 'features/imageActions/actions'; @@ -18,7 +17,6 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { const subMenu = useSubMenu(); const store = useAppStore(); const imageDTO = useImageDTOContext(); - const imageViewer = useImageViewer(); const isBusy = useCanvasIsBusySafe(); const onClickNewRasterLayerFromImage = useCallback(() => { @@ -26,65 +24,60 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { createNewCanvasEntityFromImage({ imageDTO, type: 'raster_layer', dispatch, getState }); dispatch(sentImageToCanvas()); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewControlLayerFromImage = useCallback(() => { const { dispatch, getState } = store; createNewCanvasEntityFromImage({ imageDTO, type: 'control_layer', dispatch, getState }); dispatch(sentImageToCanvas()); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewInpaintMaskFromImage = useCallback(() => { const { dispatch, getState } = store; createNewCanvasEntityFromImage({ imageDTO, type: 'inpaint_mask', dispatch, getState }); dispatch(sentImageToCanvas()); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewRegionalGuidanceFromImage = useCallback(() => { const { dispatch, getState } = store; createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance', dispatch, getState }); dispatch(sentImageToCanvas()); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); const onClickNewRegionalReferenceImageFromImage = useCallback(() => { const { dispatch, getState } = store; createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance_with_reference_image', dispatch, getState }); dispatch(sentImageToCanvas()); dispatch(setActiveTab('canvas')); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); return ( }> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx index 1e069f4263..aa68d5460e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx @@ -1,5 +1,4 @@ import { IconMenuItem } from 'common/components/IconMenuItem'; -import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,10 +7,10 @@ import { PiArrowsOutBold } from 'react-icons/pi'; export const ImageMenuItemOpenInViewer = memo(() => { const { t } = useTranslation(); const imageDTO = useImageDTOContext(); - const imageViewer = useImageViewer(); const onClick = useCallback(() => { - imageViewer.openImageInViewer(imageDTO); - }, [imageDTO, imageViewer]); + // TODO + imageDTO.image_name; + }, [imageDTO]); return ( { const { t } = useTranslation(); const store = useAppStore(); const imageDTO = useImageDTOContext(); - const imageViewer = useImageViewer(); const onClickNewGlobalReferenceImageFromImage = useCallback(() => { const { dispatch, getState } = store; const config = getDefaultRefImageConfig(getState); config.image = imageDTOToImageWithDims(imageDTO); dispatch(refImageAdded({ overrides: { config } })); - imageViewer.close(); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), status: 'success', }); - }, [imageDTO, imageViewer, store, t]); + }, [imageDTO, store, t]); return ( } onClickCapture={onClickNewGlobalReferenceImageFromImage}> 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 87ae975857..3bf55181a3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -15,7 +15,6 @@ import { firefoxDndFix } from 'features/dnd/util'; import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; import { GalleryImageHoverIcons } from 'features/gallery/components/ImageGrid/GalleryImageHoverIcons'; import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; -import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice'; import type { MouseEventHandler } from 'react'; import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; @@ -203,9 +202,6 @@ export const GalleryImage = memo(({ imageDTO }: Props) => { ); const onDoubleClick = useCallback>(() => { - // Use the atom here directly instead of the `useImageViewer` to avoid re-rendering the gallery when the viewer - // opened state changes. - $imageViewer.set(true); store.dispatch(imageToCompareChanged(null)); }, [store]); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx index 55c4a68e82..bf58669bf6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx @@ -1,5 +1,4 @@ import { DndImageIcon } from 'features/dnd/DndImageIcon'; -import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowsOutBold } from 'react-icons/pi'; @@ -10,12 +9,12 @@ type Props = { }; export const GalleryImageOpenInViewerIconButton = memo(({ imageDTO }: Props) => { - const imageViewer = useImageViewer(); const { t } = useTranslation(); const onClick = useCallback(() => { - imageViewer.openImageInViewer(imageDTO); - }, [imageDTO, imageViewer]); + // TODO + imageDTO.image_name; + }, [imageDTO]); return ( { const lastSelectedImageName = useAppSelector(selectLastSelectedImageName); const { data: lastSelectedImageDTO } = useGetImageDTOQuery(lastSelectedImageName ?? skipToken); @@ -48,78 +20,3 @@ export const ImageViewer = memo(() => { }); ImageViewer.displayName = 'ImageViewer'; - -const imageViewerContainerSx: SystemStyleObject = { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - transition: 'opacity 0.15s ease', - opacity: 1, - pointerEvents: 'auto', - '&[data-hidden="true"]': { - opacity: 0, - pointerEvents: 'none', - }, - backdropFilter: 'blur(10px) brightness(70%)', -}; - -export const ImageViewerModal = memo(() => { - const ref = useRef(null); - const imageViewer = useImageViewer(); - useOutsideClick({ - ref, - handler: imageViewer.close, - }); - - useHotkeys( - 'esc', - imageViewer.close, - { - preventDefault: true, - enabled: imageViewer.isOpen, - }, - [imageViewer.isOpen] - ); - - return ( - - - - - - - ); -}); - -ImageViewerModal.displayName = 'GatedImageViewer'; - -const ImageViewerCloseButton = memo(() => { - const { t } = useTranslation(); - const imageViewer = useImageViewer(); - useAssertSingleton('ImageViewerCloseButton'); - useHotkeys('esc', imageViewer.close); - return ( - } - variant="link" - alignSelf="stretch" - onClick={imageViewer.close} - /> - ); -}); - -ImageViewerCloseButton.displayName = 'ImageViewerCloseButton'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer2.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer2.tsx index 07cab441af..510a974a25 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer2.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer2.tsx @@ -1,20 +1,12 @@ -import { Box, Flex, IconButton, type SystemStyleObject, useOutsideClick } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppSelector } from 'app/store/storeHooks'; -import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; import { selectImageToCompare } from 'features/gallery/components/ImageViewer/common'; import { CurrentImagePreview } from 'features/gallery/components/ImageViewer/CurrentImagePreview2'; import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison'; -import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar2'; import { selectLastSelectedImageName } from 'features/gallery/store/gallerySelectors'; -import { memo, useRef } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { PiXBold } from 'react-icons/pi'; +import { memo } from 'react'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; -import { useImageViewer } from './useImageViewer'; - // type Props = { // closeButton?: ReactNode; // }; @@ -48,78 +40,3 @@ export const ImageViewer = memo(() => { }); ImageViewer.displayName = 'ImageViewer'; - -const imageViewerContainerSx: SystemStyleObject = { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - transition: 'opacity 0.15s ease', - opacity: 1, - pointerEvents: 'auto', - '&[data-hidden="true"]': { - opacity: 0, - pointerEvents: 'none', - }, - backdropFilter: 'blur(10px) brightness(70%)', -}; - -export const ImageViewerModal = memo(() => { - const ref = useRef(null); - const imageViewer = useImageViewer(); - useOutsideClick({ - ref, - handler: imageViewer.close, - }); - - useHotkeys( - 'esc', - imageViewer.close, - { - preventDefault: true, - enabled: imageViewer.isOpen, - }, - [imageViewer.isOpen] - ); - - return ( - - - - - - - ); -}); - -ImageViewerModal.displayName = 'GatedImageViewer'; - -const ImageViewerCloseButton = memo(() => { - const { t } = useTranslation(); - const imageViewer = useImageViewer(); - useAssertSingleton('ImageViewerCloseButton'); - useHotkeys('esc', imageViewer.close); - return ( - } - variant="link" - alignSelf="stretch" - onClick={imageViewer.close} - /> - ); -}); - -ImageViewerCloseButton.displayName = 'ImageViewerCloseButton'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts deleted file mode 100644 index 038c5ee607..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useAppDispatch } from 'app/store/storeHooks'; -import { buildUseBoolean } from 'common/hooks/useBoolean'; -import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; -import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice'; -import { useCallback } from 'react'; -import type { ImageDTO } from 'services/api/types'; - -/** - * There's a race condition that causes the canvas to not fit to layers on the very first app startup. - * - * The canvas stage uses a resize observer to fit the stage to the container, and on the first resize event, it also - * fits the layers to the stage. Subsequent resize events only fit the stage to the container, they do not fit layers - * to the stage. - * - * On the very first app startup (new user or after they reset all web UI state), the resizable panels library needs - * to do one extra resize as it initializes and figures out its target size. At this time, the canvas stage has already - * done its one-time fit layers to stage, so the canvas stage does not fit layers to the stage again. - * - * For the end user, this means that the bbox is not centered in the canvas stage on the very first app startup. On - * all subsequent app startups, the bbox is centered in the canvas stage. - * - * We can hack around this, thanks to the fact that the image viewer is always opened on the first app startup. By the - * time the user closes it, the resizable panels library has already done its one extra resize and the DOM layout has - * stabilized. So we can track the first time the image viewer is closed and fit the layers to the stage at that time, - * ensuring that the bbox is centered in the canvas stage on that first app startup. - * - * TODO(psyche): Figure out a better way to do handle this... - */ -let didCloseImageViewer = false; -const api = buildUseBoolean(false); -const useImageViewerState = api[0]; -export const $imageViewer = api[1]; -export const toggleImageViewer = () => $imageViewer.set(!$imageViewer.get()); - -export const useImageViewer = () => { - const dispatch = useAppDispatch(); - const canvasManager = useCanvasManagerSafe(); - const imageViewerState = useImageViewerState(); - const close = useCallback(() => { - if (!didCloseImageViewer && canvasManager) { - didCloseImageViewer = true; - canvasManager.stage.fitLayersToStage(); - } - imageViewerState.setFalse(); - }, [canvasManager, imageViewerState]); - const openImageInViewer = useCallback( - (imageDTO: ImageDTO) => { - dispatch(imageToCompareChanged(null)); - dispatch(imageSelected(imageDTO)); - imageViewerState.setTrue(); - }, - [dispatch, imageViewerState] - ); - - return { - isOpen: imageViewerState.isTrue, - open: imageViewerState.setTrue, - close, - toggle: imageViewerState.toggle, - openImageInViewer, - }; -};