mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): revised focus handling (attempt 1)
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useFocusRegion } from 'common/hooks/interactionScopes';
|
||||
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
|
||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
|
||||
import { EntityListSelectedEntityActionBar } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBar';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectHasEntities } from 'features/controlLayers/store/selectors';
|
||||
import { memo } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
|
||||
export const CanvasLayersPanelContent = memo(() => {
|
||||
const hasEntities = useAppSelector(selectHasEntities);
|
||||
const layersPanelFocusRef = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('layersPanel', layersPanelFocusRef);
|
||||
|
||||
return (
|
||||
<CanvasManagerProviderGate>
|
||||
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||
<EntityListSelectedEntityActionBar />
|
||||
<Divider py={0} />
|
||||
{!hasEntities && <CanvasAddEntityButtons />}
|
||||
{hasEntities && <CanvasEntityList />}
|
||||
</Flex>
|
||||
</CanvasManagerProviderGate>
|
||||
<Flex ref={layersPanelFocusRef} flexDir="column" gap={2} w="full" h="full">
|
||||
<EntityListSelectedEntityActionBar />
|
||||
<Divider py={0} />
|
||||
{!hasEntities && <CanvasAddEntityButtons />}
|
||||
{hasEntities && <CanvasEntityList />}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContextMenu, Flex, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { useFocusRegion } from 'common/hooks/interactionScopes';
|
||||
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
|
||||
import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus';
|
||||
import { CanvasAlertsSendingToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSendingTo';
|
||||
@@ -35,7 +35,7 @@ export const CanvasMainPanelContent = memo(() => {
|
||||
);
|
||||
}, []);
|
||||
|
||||
useScopeOnFocus('canvas', ref);
|
||||
useFocusRegion('canvas', ref);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useDndContext } from '@dnd-kit/core';
|
||||
import { Box, Button, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import {
|
||||
$canvasRightPanelTabIndex,
|
||||
selectCanvasRightPanelGalleryTab,
|
||||
@@ -18,9 +18,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CanvasRightPanel = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const tabIndex = useStore($canvasRightPanelTabIndex);
|
||||
useScopeOnFocus('gallery', ref);
|
||||
const imageViewer = useImageViewer();
|
||||
const onClickViewerToggleButton = useCallback(() => {
|
||||
if ($canvasRightPanelTabIndex.get() !== 1) {
|
||||
@@ -46,7 +44,9 @@ export const CanvasRightPanel = memo(() => {
|
||||
</TabList>
|
||||
<TabPanels w="full" h="full">
|
||||
<TabPanel w="full" h="full" p={0} pt={3}>
|
||||
<CanvasLayersPanelContent />
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasLayersPanelContent />
|
||||
</CanvasManagerProviderGate>
|
||||
</TabPanel>
|
||||
<TabPanel w="full" h="full" p={0} pt={3}>
|
||||
<GalleryPanelContent />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ButtonGroup } from '@invoke-ai/ui-library';
|
||||
import { useScopeOnMount } from 'common/hooks/interactionScopes';
|
||||
import { useFocusRegionOnMount } from 'common/hooks/interactionScopes';
|
||||
import { StagingAreaToolbarAcceptButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton';
|
||||
import { StagingAreaToolbarDiscardAllButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton';
|
||||
import { StagingAreaToolbarDiscardSelectedButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton';
|
||||
@@ -11,7 +11,7 @@ import { StagingAreaToolbarToggleShowResultsButton } from 'features/controlLayer
|
||||
import { memo } from 'react';
|
||||
|
||||
export const StagingAreaToolbar = memo(() => {
|
||||
useScopeOnMount('stagingArea');
|
||||
useFocusRegionOnMount('stagingArea');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||
import {
|
||||
@@ -25,7 +25,7 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const imageCount = useAppSelector(selectImageCount);
|
||||
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
|
||||
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
||||
const isCanvasActive = useStore(FOCUS_REGIONS.canvas.$isFocused);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import {
|
||||
selectImageCount,
|
||||
@@ -17,7 +17,7 @@ export const StagingAreaToolbarNextButton = memo(() => {
|
||||
const canvasManager = useCanvasManager();
|
||||
const imageCount = useAppSelector(selectImageCount);
|
||||
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
|
||||
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
||||
const isCanvasActive = useStore(FOCUS_REGIONS.canvas.$isFocused);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import {
|
||||
selectImageCount,
|
||||
@@ -17,7 +17,7 @@ export const StagingAreaToolbarPrevButton = memo(() => {
|
||||
const canvasManager = useCanvasManager();
|
||||
const imageCount = useAppSelector(selectImageCount);
|
||||
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
|
||||
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
||||
const isCanvasActive = useStore(FOCUS_REGIONS.canvas.$isFocused);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
@@ -11,7 +11,7 @@ import { PiArrowsOutBold } from 'react-icons/pi';
|
||||
export const CanvasToolbarResetViewButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useCanvasManager();
|
||||
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
||||
const isCanvasActive = useStore(FOCUS_REGIONS.canvas.$isFocused);
|
||||
const imageViewer = useImageViewer();
|
||||
|
||||
useRegisteredHotkeys({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Button, Collapse, Divider, Flex, IconButton, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { useFocusRegion } from 'common/hooks/interactionScopes';
|
||||
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
||||
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
|
||||
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||
@@ -26,8 +26,8 @@ const GalleryPanelContent = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('gallery', ref);
|
||||
const galleryPanelFocusRef = useRef<HTMLDivElement>(null);
|
||||
useFocusRegion('galleryPanel', galleryPanelFocusRef);
|
||||
|
||||
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
@@ -50,7 +50,7 @@ const GalleryPanelContent = () => {
|
||||
}, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]);
|
||||
|
||||
return (
|
||||
<Flex ref={ref} position="relative" flexDirection="column" h="full" w="full" tabIndex={-1}>
|
||||
<Flex ref={galleryPanelFocusRef} position="relative" flexDirection="column" h="full" w="full" tabIndex={-1}>
|
||||
<Flex alignItems="center" w="full">
|
||||
<Flex w="25%">
|
||||
<Button
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import { Tag, TagCloseButton, TagLabel } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { $activeScopes } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
|
||||
import { selectionChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
import { $isRightPanelOpen } from 'features/ui/store/uiSlice';
|
||||
import { computed } from 'nanostores';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const $isSelectAllEnabled = computed([$activeScopes, $isRightPanelOpen], (activeScopes, isGalleryPanelOpen) => {
|
||||
return activeScopes.has('gallery') && !activeScopes.has('workflows') && isGalleryPanelOpen;
|
||||
});
|
||||
|
||||
export const GallerySelectionCountTag = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { selection } = useAppSelector((s) => s.gallery);
|
||||
const { t } = useTranslation();
|
||||
const { imageDTOs } = useGalleryImages();
|
||||
const isSelectAllEnabled = useStore($isSelectAllEnabled);
|
||||
const isFocusedOnGallery = useStore(FOCUS_REGIONS.galleryPanel.$isFocused);
|
||||
|
||||
const onClearSelection = useCallback(() => {
|
||||
const firstImage = selection[0];
|
||||
@@ -36,16 +30,16 @@ export const GallerySelectionCountTag = () => {
|
||||
id: 'selectAllOnPage',
|
||||
category: 'gallery',
|
||||
callback: onSelectPage,
|
||||
options: { preventDefault: true, enabled: isSelectAllEnabled },
|
||||
dependencies: [onSelectPage, isSelectAllEnabled],
|
||||
options: { preventDefault: true, enabled: isFocusedOnGallery },
|
||||
dependencies: [onSelectPage, isFocusedOnGallery],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
id: 'clearSelection',
|
||||
category: 'gallery',
|
||||
callback: onClearSelection,
|
||||
options: { enabled: selection.length > 0 && isSelectAllEnabled },
|
||||
dependencies: [onClearSelection, selection, isSelectAllEnabled],
|
||||
options: { enabled: selection.length > 0 && isFocusedOnGallery },
|
||||
dependencies: [onClearSelection, selection, isFocusedOnGallery],
|
||||
});
|
||||
|
||||
if (selection.length <= 1) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useStore } from '@nanostores/react';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { adHocPostProcessingRequested } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
|
||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||
@@ -45,7 +45,7 @@ const CurrentImageButtons = () => {
|
||||
const isUpscalingEnabled = useFeatureStatus('upscaling');
|
||||
const isQueueMutationInProgress = useIsQueueMutationInProgress();
|
||||
const { t } = useTranslation();
|
||||
const isImageViewerActive = useStore(INTERACTION_SCOPES.imageViewer.$isActive);
|
||||
const isImageViewerActive = useStore(FOCUS_REGIONS.imageViewer.$isFocused);
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
|
||||
|
||||
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus, useScopeOnMount } from 'common/hooks/interactionScopes';
|
||||
import { useFocusRegion, useFocusRegionOnMount } from 'common/hooks/interactionScopes';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { CanvasAlertsSendingToCanvas } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSendingTo';
|
||||
import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar';
|
||||
@@ -10,7 +10,7 @@ import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewe
|
||||
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
|
||||
import { selectHasImageToCompare } from 'features/gallery/store/gallerySelectors';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
@@ -27,12 +27,8 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
const hasImageToCompare = useAppSelector(selectHasImageToCompare);
|
||||
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('imageViewer', ref);
|
||||
useScopeOnMount('imageViewer');
|
||||
|
||||
useEffect(() => {
|
||||
ref?.current?.focus();
|
||||
}, []);
|
||||
useFocusRegion('imageViewer', ref);
|
||||
useFocusRegionOnMount('imageViewer');
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { $activeScopes, INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
||||
import { $focusedRegion, FOCUS_REGIONS } from 'common/hooks/interactionScopes';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { $canvasRightPanelTab } from 'features/controlLayers/store/ephemeral';
|
||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||
@@ -9,21 +9,9 @@ import { useGalleryPagination } from 'features/gallery/hooks/useGalleryPaginatio
|
||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { $isRightPanelOpen } from 'features/ui/store/uiSlice';
|
||||
import { computed } from 'nanostores';
|
||||
import { useMemo } from 'react';
|
||||
import { useListImagesQuery } from 'services/api/endpoints/images';
|
||||
|
||||
const $leftRightHotkeysEnabled = computed($activeScopes, (activeScopes) => {
|
||||
// The left and right hotkeys can be used when the gallery is focused and the canvas is not focused, OR when the image viewer is focused.
|
||||
return !activeScopes.has('canvas') || activeScopes.has('imageViewer');
|
||||
});
|
||||
|
||||
const $upDownHotkeysEnabled = computed([$activeScopes, $isRightPanelOpen], (activeScopes, isGalleryPanelOpen) => {
|
||||
// The up and down hotkeys can be used when the gallery is focused and the canvas is not focused, and the gallery panel is open.
|
||||
return !activeScopes.has('canvas') && isGalleryPanelOpen;
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers gallery hotkeys. This hook is a singleton.
|
||||
*/
|
||||
@@ -34,11 +22,10 @@ export const useGalleryHotkeys = () => {
|
||||
const selection = useAppSelector((s) => s.gallery.selection);
|
||||
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
||||
const queryResult = useListImagesQuery(queryArgs);
|
||||
const leftRightHotkeysEnabled = useStore($leftRightHotkeysEnabled);
|
||||
const upDownHotkeysEnabled = useStore($upDownHotkeysEnabled);
|
||||
const canvasRightPanelTab = useStore($canvasRightPanelTab);
|
||||
const appTab = useAppSelector(selectActiveTab);
|
||||
const isWorkflowsScopeActive = useStore(INTERACTION_SCOPES.workflows.$isActive);
|
||||
const isWorkflowsScopeActive = useStore(FOCUS_REGIONS.workflows.$isFocused);
|
||||
const focusedRegion = useStore($focusedRegion);
|
||||
|
||||
// When we are on the canvas tab, we need to disable the delete hotkey when the user is focused on the layers tab in
|
||||
// the right hand panel, because the same hotkey is used to delete layers.
|
||||
@@ -70,15 +57,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleLeftImage(false);
|
||||
},
|
||||
options: { preventDefault: true, enabled: leftRightHotkeysEnabled },
|
||||
dependencies: [
|
||||
handleLeftImage,
|
||||
isOnFirstImageOfView,
|
||||
goPrev,
|
||||
isPrevEnabled,
|
||||
queryResult.isFetching,
|
||||
leftRightHotkeysEnabled,
|
||||
],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
|
||||
dependencies: [handleLeftImage, isOnFirstImageOfView, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -93,15 +73,8 @@ export const useGalleryHotkeys = () => {
|
||||
handleRightImage(false);
|
||||
}
|
||||
},
|
||||
options: { preventDefault: true, enabled: leftRightHotkeysEnabled },
|
||||
dependencies: [
|
||||
isOnLastImageOfView,
|
||||
goNext,
|
||||
isNextEnabled,
|
||||
queryResult.isFetching,
|
||||
handleRightImage,
|
||||
leftRightHotkeysEnabled,
|
||||
],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
|
||||
dependencies: [isOnLastImageOfView, goNext, isNextEnabled, queryResult.isFetching, handleRightImage, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -114,8 +87,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleUpImage(false);
|
||||
},
|
||||
options: { preventDefault: true, enabled: upDownHotkeysEnabled },
|
||||
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, upDownHotkeysEnabled],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
|
||||
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -128,8 +101,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleDownImage(false);
|
||||
},
|
||||
options: { preventDefault: true, enabled: upDownHotkeysEnabled },
|
||||
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, upDownHotkeysEnabled],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
|
||||
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -142,15 +115,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleLeftImage(true);
|
||||
},
|
||||
options: { preventDefault: true, enabled: leftRightHotkeysEnabled },
|
||||
dependencies: [
|
||||
handleLeftImage,
|
||||
isOnFirstImageOfView,
|
||||
goPrev,
|
||||
isPrevEnabled,
|
||||
queryResult.isFetching,
|
||||
leftRightHotkeysEnabled,
|
||||
],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
|
||||
dependencies: [handleLeftImage, isOnFirstImageOfView, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -165,15 +131,8 @@ export const useGalleryHotkeys = () => {
|
||||
handleRightImage(true);
|
||||
}
|
||||
},
|
||||
options: { preventDefault: true, enabled: leftRightHotkeysEnabled },
|
||||
dependencies: [
|
||||
isOnLastImageOfView,
|
||||
goNext,
|
||||
isNextEnabled,
|
||||
queryResult.isFetching,
|
||||
handleRightImage,
|
||||
leftRightHotkeysEnabled,
|
||||
],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
|
||||
dependencies: [isOnLastImageOfView, goNext, isNextEnabled, queryResult.isFetching, handleRightImage, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -186,8 +145,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleUpImage(true);
|
||||
},
|
||||
options: { preventDefault: true, enabled: upDownHotkeysEnabled },
|
||||
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, upDownHotkeysEnabled],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
|
||||
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -200,8 +159,8 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
handleDownImage(true);
|
||||
},
|
||||
options: { preventDefault: true, enabled: upDownHotkeysEnabled },
|
||||
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, upDownHotkeysEnabled],
|
||||
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
|
||||
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, focusedRegion],
|
||||
});
|
||||
|
||||
useRegisteredHotkeys({
|
||||
@@ -213,7 +172,12 @@ export const useGalleryHotkeys = () => {
|
||||
}
|
||||
dispatch(imagesToDeleteSelected(selection));
|
||||
},
|
||||
options: { enabled: leftRightHotkeysEnabled && isDeleteEnabledByTab && !isWorkflowsScopeActive },
|
||||
dependencies: [leftRightHotkeysEnabled, isDeleteEnabledByTab, selection, isWorkflowsScopeActive],
|
||||
options: {
|
||||
enabled:
|
||||
(focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer') &&
|
||||
isDeleteEnabledByTab &&
|
||||
!isWorkflowsScopeActive,
|
||||
},
|
||||
dependencies: [focusedRegion, isDeleteEnabledByTab, selection, isWorkflowsScopeActive],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'reactflow/dist/style.css';
|
||||
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { useFocusRegion } from 'common/hooks/interactionScopes';
|
||||
import { AddNodeCmdk } from 'features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk';
|
||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||
import WorkflowEditorSettings from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
|
||||
@@ -21,7 +21,7 @@ const NodeEditor = () => {
|
||||
const { data, isLoading } = useGetOpenAPISchemaQuery();
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('workflows', ref);
|
||||
useFocusRegion('workflows', ref);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
|
||||
import { INTERACTION_SCOPES, useScopeImperativeApi, useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { $focusedRegion, FOCUS_REGIONS, useFocusRegion } from 'common/hooks/interactionScopes';
|
||||
import { useConnection } from 'features/nodes/hooks/useConnection';
|
||||
import { useCopyPaste } from 'features/nodes/hooks/useCopyPaste';
|
||||
import { useSyncExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||
@@ -89,9 +89,8 @@ export const Flow = memo(() => {
|
||||
const cancelConnection = useReactFlowStore(selectCancelConnection);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const store = useAppStore();
|
||||
const isWorkflowsActive = useStore(INTERACTION_SCOPES.workflows.$isActive);
|
||||
const workflowsScopeApi = useScopeImperativeApi('workflows');
|
||||
useScopeOnFocus('workflows', flowWrapper);
|
||||
const isWorkflowsActive = useStore(FOCUS_REGIONS.workflows.$isFocused);
|
||||
useFocusRegion('workflows', flowWrapper);
|
||||
|
||||
useWorkflowWatcher();
|
||||
useSyncExecutionState();
|
||||
@@ -129,8 +128,8 @@ export const Flow = memo(() => {
|
||||
const { onCloseGlobal } = useGlobalMenuClose();
|
||||
const handlePaneClick = useCallback(() => {
|
||||
onCloseGlobal();
|
||||
workflowsScopeApi.add();
|
||||
}, [onCloseGlobal, workflowsScopeApi]);
|
||||
$focusedRegion.set('workflows');
|
||||
}, [onCloseGlobal]);
|
||||
|
||||
const onInit: OnInit = useCallback((flow) => {
|
||||
$flow.set(flow);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { CanvasMainPanelContent } from 'features/controlLayers/components/CanvasMainPanelContent';
|
||||
import { CanvasRightPanel } from 'features/controlLayers/components/CanvasRightPanel';
|
||||
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
|
||||
@@ -40,9 +39,6 @@ const onLeftPanelCollapse = (isCollapsed: boolean) => $isLeftPanelOpen.set(!isCo
|
||||
const onRightPanelCollapse = (isCollapsed: boolean) => $isRightPanelOpen.set(!isCollapsed);
|
||||
|
||||
export const AppContent = memo(() => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('gallery', ref);
|
||||
|
||||
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
|
||||
const withLeftPanel = useAppSelector(selectWithLeftPanel);
|
||||
@@ -119,7 +115,7 @@ export const AppContent = memo(() => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex ref={ref} id="invoke-app-tabs" w="full" h="full" gap={4} p={4}>
|
||||
<Flex id="invoke-app-tabs" w="full" h="full" gap={4} p={4}>
|
||||
<VerticalNavBar />
|
||||
<Flex position="relative" w="full" h="full" gap={4} minW={0}>
|
||||
<PanelGroup
|
||||
|
||||
Reference in New Issue
Block a user