feat(ui): revised focus handling (attempt 3, wip)

This commit is contained in:
psychedelicious
2024-09-30 17:10:41 +10:00
parent 48311f38ba
commit 8cf0d8c8d3
12 changed files with 145 additions and 124 deletions

View File

@@ -1,3 +1,4 @@
import { useStore } from '@nanostores/react';
import { logger } from 'app/logging/logger';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import type { Atom } from 'nanostores';
@@ -7,10 +8,10 @@ import { useEffect } from 'react';
const log = logger('system');
const REGION_NAMES = ['galleryPanel', 'layersPanel', 'canvas', 'workflows', 'imageViewer'] as const;
const REGION_NAMES = ['gallery', 'layers', 'canvas', 'workflows', 'viewer', 'settings'] as const;
type FocusRegionName = (typeof REGION_NAMES)[number];
type FocusRegionData = { name: FocusRegionName; mounted: number; targets: Set<HTMLElement> };
type FocusRegionData = { name: FocusRegionName; targets: Set<HTMLElement> };
type FocusRegionState = {
focusedRegion: FocusRegionName | null;
regions: Record<FocusRegionName, FocusRegionData>;
@@ -18,7 +19,7 @@ type FocusRegionState = {
const initialData = REGION_NAMES.reduce(
(state, region) => {
state.regions[region] = { name: region, mounted: 0, targets: new Set() };
state.regions[region] = { name: region, targets: new Set() };
return state;
},
{
@@ -31,26 +32,33 @@ const $focusRegionState = deepMap<FocusRegionState>(initialData);
export const $focusedRegion = computed($focusRegionState, (regions) => regions.focusedRegion);
export const FOCUS_REGIONS = REGION_NAMES.reduce(
(acc, region) => {
acc[`$${region}`] = computed($focusRegionState, (state) => ({
isFocused: state.focusedRegion === region,
isMounted: state.regions[region].mounted > 0,
}));
acc[`$${region}`] = computed($focusRegionState, (state) => state.focusedRegion === region);
return acc;
},
{} as Record<`$${FocusRegionName}`, Atom<{ isFocused: boolean; isMounted: boolean }>>
{} as Record<`$${FocusRegionName}`, Atom<boolean>>
);
export const setFocus = (region: FocusRegionName | null) => {
const setFocus = (region: FocusRegionName | null) => {
$focusRegionState.setKey('focusedRegion', region);
log.trace(`Focus changed: ${region}`);
};
export const useFocusRegion = (region: FocusRegionName, ref: RefObject<HTMLElement>) => {
type UseFocusRegionOptions = {
focusOnMount?: boolean;
};
export const useFocusRegion = (
region: FocusRegionName,
ref: RefObject<HTMLElement>,
options?: UseFocusRegionOptions
) => {
useEffect(() => {
if (!ref.current) {
return;
}
const { focusOnMount = false } = { focusOnMount: false, ...options };
const element = ref.current;
const regionData = $focusRegionState.get().regions[region];
@@ -59,36 +67,25 @@ export const useFocusRegion = (region: FocusRegionName, ref: RefObject<HTMLEleme
targets.add(element);
$focusRegionState.setKey(`regions.${region}.targets`, targets);
if (focusOnMount) {
setFocus(region);
}
return () => {
const regionData = $focusRegionState.get().regions[region];
const targets = new Set(regionData.targets);
targets.delete(element);
$focusRegionState.setKey(`regions.${region}.targets`, targets);
};
}, [ref, region]);
};
export const useFocusRegionOnMount = (region: FocusRegionName) => {
useEffect(() => {
const mounted = $focusRegionState.get().regions[region].mounted + 1;
$focusRegionState.setKey(`regions.${region}.mounted`, mounted);
setFocus(region);
log.trace(`Focus region ${region} mounted, count: ${mounted}`);
return () => {
let mounted = $focusRegionState.get().regions[region].mounted - 1;
if (mounted < 0) {
log.warn(`Focus region ${region} mounted count is negative: ${mounted}!`);
mounted = 0;
} else {
log.trace(`Focus region ${region} unmounted, count: ${mounted}`);
}
$focusRegionState.setKey(`regions.${region}.mounted`, mounted);
if (mounted === 0) {
if (targets.size === 0 && $focusRegionState.get().focusedRegion === region) {
setFocus(null);
}
};
}, [region]);
}, [options, ref, region]);
};
export const useIsRegionFocused = (region: FocusRegionName) => {
return useStore(FOCUS_REGIONS[`$${region}`]);
};
const onFocus = (_: FocusEvent) => {

View File

@@ -10,7 +10,7 @@ import { memo, useRef } from 'react';
export const CanvasLayersPanelContent = memo(() => {
const hasEntities = useAppSelector(selectHasEntities);
const layersPanelFocusRef = useRef<HTMLDivElement>(null);
useFocusRegion('layersPanel', layersPanelFocusRef);
useFocusRegion('layers', layersPanelFocusRef);
return (
<Flex ref={layersPanelFocusRef} flexDir="column" gap={2} w="full" h="full">

View File

@@ -1,7 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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 canvasFocus = useStore(FOCUS_REGIONS.$canvas);
const isCanvasFocused = useIsRegionFocused('canvas');
const { t } = useTranslation();
@@ -50,9 +50,9 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
acceptSelected,
{
preventDefault: true,
enabled: canvasFocus.isFocused && shouldShowStagedImage && imageCount > 1,
enabled: isCanvasFocused && shouldShowStagedImage && imageCount > 1,
},
[canvasFocus, shouldShowStagedImage, imageCount]
[isCanvasFocused, shouldShowStagedImage, imageCount]
);
return (

View File

@@ -1,7 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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 canvasScope = useStore(FOCUS_REGIONS.$canvas);
const isCanvasFocused = useIsRegionFocused('canvas');
const { t } = useTranslation();
@@ -30,9 +30,9 @@ export const StagingAreaToolbarNextButton = memo(() => {
selectNext,
{
preventDefault: true,
enabled: canvasScope.isFocused && shouldShowStagedImage && imageCount > 1,
enabled: isCanvasFocused && shouldShowStagedImage && imageCount > 1,
},
[canvasScope, shouldShowStagedImage, imageCount]
[isCanvasFocused, shouldShowStagedImage, imageCount]
);
return (

View File

@@ -1,7 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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 canvasScope = useStore(FOCUS_REGIONS.$canvas);
const isCanvasFocused = useIsRegionFocused('canvas');
const { t } = useTranslation();
@@ -30,9 +30,9 @@ export const StagingAreaToolbarPrevButton = memo(() => {
selectPrev,
{
preventDefault: true,
enabled: canvasScope.isFocused && shouldShowStagedImage && imageCount > 1,
enabled: isCanvasFocused && shouldShowStagedImage && imageCount > 1,
},
[canvasScope, shouldShowStagedImage, imageCount]
[isCanvasFocused, shouldShowStagedImage, imageCount]
);
return (

View File

@@ -1,6 +1,5 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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,50 +10,50 @@ import { PiArrowsOutBold } from 'react-icons/pi';
export const CanvasToolbarResetViewButton = memo(() => {
const { t } = useTranslation();
const canvasManager = useCanvasManager();
const canvasScope = useStore(FOCUS_REGIONS.$canvas);
const isCanvasFocused = useIsRegionFocused('canvas');
const imageViewer = useImageViewer();
useRegisteredHotkeys({
id: 'fitLayersToCanvas',
category: 'canvas',
callback: canvasManager.stage.fitLayersToStage,
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
useRegisteredHotkeys({
id: 'fitBboxToCanvas',
category: 'canvas',
callback: canvasManager.stage.fitBboxToStage,
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
useRegisteredHotkeys({
id: 'setZoomTo100Percent',
category: 'canvas',
callback: () => canvasManager.stage.setScale(1),
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
useRegisteredHotkeys({
id: 'setZoomTo200Percent',
category: 'canvas',
callback: () => canvasManager.stage.setScale(2),
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
useRegisteredHotkeys({
id: 'setZoomTo400Percent',
category: 'canvas',
callback: () => canvasManager.stage.setScale(4),
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
useRegisteredHotkeys({
id: 'setZoomTo800Percent',
category: 'canvas',
callback: () => canvasManager.stage.setScale(8),
options: { enabled: canvasScope.isFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [canvasScope, imageViewer.isOpen],
options: { enabled: isCanvasFocused && !imageViewer.isOpen, preventDefault: true },
dependencies: [isCanvasFocused, imageViewer.isOpen],
});
return (

View File

@@ -27,7 +27,7 @@ const GalleryPanelContent = () => {
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const galleryPanelFocusRef = useRef<HTMLDivElement>(null);
useFocusRegion('galleryPanel', galleryPanelFocusRef);
useFocusRegion('gallery', galleryPanelFocusRef);
const boardsListPanelOptions = useMemo<UsePanelOptions>(
() => ({

View File

@@ -1,7 +1,6 @@
import { Tag, TagCloseButton, TagLabel } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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';
@@ -13,7 +12,7 @@ export const GallerySelectionCountTag = () => {
const { selection } = useAppSelector((s) => s.gallery);
const { t } = useTranslation();
const { imageDTOs } = useGalleryImages();
const galleryPanelFocus = useStore(FOCUS_REGIONS.$galleryPanel);
const isGalleryFocused = useIsRegionFocused('gallery');
const onClearSelection = useCallback(() => {
const firstImage = selection[0];
@@ -30,16 +29,16 @@ export const GallerySelectionCountTag = () => {
id: 'selectAllOnPage',
category: 'gallery',
callback: onSelectPage,
options: { preventDefault: true, enabled: galleryPanelFocus.isFocused },
dependencies: [onSelectPage, galleryPanelFocus],
options: { preventDefault: true, enabled: isGalleryFocused },
dependencies: [onSelectPage, isGalleryFocused],
});
useRegisteredHotkeys({
id: 'clearSelection',
category: 'gallery',
callback: onClearSelection,
options: { enabled: selection.length > 0 && galleryPanelFocus.isFocused },
dependencies: [onClearSelection, selection, galleryPanelFocus],
options: { enabled: selection.length > 0 && isGalleryFocused },
dependencies: [onClearSelection, selection, isGalleryFocused],
});
if (selection.length <= 1) {

View File

@@ -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 { FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } 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';
@@ -18,7 +18,6 @@ import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/us
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
import { size } from 'lodash-es';
import { computed } from 'nanostores';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -33,13 +32,6 @@ import {
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { $isConnected, $progressImage } from 'services/events/stores';
const $imageViewerOrGalleryIsFocused = computed(
[FOCUS_REGIONS.$imageViewer, FOCUS_REGIONS.$galleryPanel],
(imageViewer, galleryPanel) => {
return imageViewer.isFocused || galleryPanel.isFocused;
}
);
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const isConnected = useStore($isConnected);
@@ -53,7 +45,8 @@ const CurrentImageButtons = () => {
const isUpscalingEnabled = useFeatureStatus('upscaling');
const isQueueMutationInProgress = useIsQueueMutationInProgress();
const { t } = useTranslation();
const imageViewerFocus = useStore($imageViewerOrGalleryIsFocused);
const isGalleryFocused = useIsRegionFocused('gallery');
const isViewerFocused = useIsRegionFocused('viewer');
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
@@ -92,50 +85,50 @@ const CurrentImageButtons = () => {
id: 'loadWorkflow',
category: 'viewer',
callback: handleLoadWorkflow,
options: { enabled: imageViewerFocus },
dependencies: [handleLoadWorkflow, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [handleLoadWorkflow, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'recallAll',
category: 'viewer',
callback: recallAll,
options: { enabled: imageViewerFocus },
dependencies: [recallAll, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [recallAll, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'recallSeed',
category: 'viewer',
callback: recallSeed,
options: { enabled: imageViewerFocus },
dependencies: [recallSeed, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [recallSeed, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'recallPrompts',
category: 'viewer',
callback: recallPrompts,
options: { enabled: imageViewerFocus },
dependencies: [recallPrompts, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [recallPrompts, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'remix',
category: 'viewer',
callback: remix,
options: { enabled: imageViewerFocus },
dependencies: [remix, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [remix, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'useSize',
category: 'viewer',
callback: handleUseSize,
options: { enabled: imageViewerFocus },
dependencies: [handleUseSize, imageViewerFocus],
options: { enabled: isGalleryFocused || isViewerFocused },
dependencies: [handleUseSize, isGalleryFocused, isViewerFocused],
});
useRegisteredHotkeys({
id: 'runPostprocessing',
category: 'viewer',
callback: handleClickUpscale,
options: { enabled: Boolean(isUpscalingEnabled && imageViewerFocus && isConnected) },
dependencies: [isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected, imageViewerFocus],
options: { enabled: Boolean(isUpscalingEnabled && isViewerFocused && isConnected) },
dependencies: [isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected, isViewerFocused],
});
return (

View File

@@ -1,6 +1,6 @@
import { Box, Flex, IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useFocusRegion, useFocusRegionOnMount } from 'common/hooks/interactionScopes';
import { useFocusRegion } 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';
@@ -22,13 +22,16 @@ type Props = {
closeButton?: ReactNode;
};
const useFocusRegionOptions = {
focusOnMount: true,
};
export const ImageViewer = memo(({ closeButton }: Props) => {
useAssertSingleton('ImageViewer');
const hasImageToCompare = useAppSelector(selectHasImageToCompare);
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
const ref = useRef<HTMLDivElement>(null);
useFocusRegion('imageViewer', ref);
useFocusRegionOnMount('imageViewer');
useFocusRegion('viewer', ref, useFocusRegionOptions);
return (
<Flex

View File

@@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $focusedRegion, FOCUS_REGIONS } from 'common/hooks/interactionScopes';
import { useIsRegionFocused } from 'common/hooks/interactionScopes';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { $canvasRightPanelTab } from 'features/controlLayers/store/ephemeral';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
@@ -24,8 +24,9 @@ export const useGalleryHotkeys = () => {
const queryResult = useListImagesQuery(queryArgs);
const canvasRightPanelTab = useStore($canvasRightPanelTab);
const appTab = useAppSelector(selectActiveTab);
const workflowsFocus = useStore(FOCUS_REGIONS.$workflows);
const focusedRegion = useStore($focusedRegion);
const isWorkflowsFocused = useIsRegionFocused('workflows');
const isGalleryFocused = useIsRegionFocused('gallery');
const isImageViewerFocused = useIsRegionFocused('viewer');
// 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.
@@ -57,8 +58,16 @@ export const useGalleryHotkeys = () => {
}
handleLeftImage(false);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
dependencies: [handleLeftImage, isOnFirstImageOfView, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused || isImageViewerFocused },
dependencies: [
handleLeftImage,
isOnFirstImageOfView,
goPrev,
isPrevEnabled,
queryResult.isFetching,
isGalleryFocused,
isImageViewerFocused,
],
});
useRegisteredHotkeys({
@@ -73,8 +82,16 @@ export const useGalleryHotkeys = () => {
handleRightImage(false);
}
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
dependencies: [isOnLastImageOfView, goNext, isNextEnabled, queryResult.isFetching, handleRightImage, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused || isImageViewerFocused },
dependencies: [
isOnLastImageOfView,
goNext,
isNextEnabled,
queryResult.isFetching,
handleRightImage,
isGalleryFocused,
isImageViewerFocused,
],
});
useRegisteredHotkeys({
@@ -87,8 +104,8 @@ export const useGalleryHotkeys = () => {
}
handleUpImage(false);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused },
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, isGalleryFocused],
});
useRegisteredHotkeys({
@@ -101,8 +118,8 @@ export const useGalleryHotkeys = () => {
}
handleDownImage(false);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused },
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, isGalleryFocused],
});
useRegisteredHotkeys({
@@ -115,8 +132,16 @@ export const useGalleryHotkeys = () => {
}
handleLeftImage(true);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
dependencies: [handleLeftImage, isOnFirstImageOfView, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused || isImageViewerFocused },
dependencies: [
handleLeftImage,
isOnFirstImageOfView,
goPrev,
isPrevEnabled,
queryResult.isFetching,
isGalleryFocused,
isImageViewerFocused,
],
});
useRegisteredHotkeys({
@@ -131,8 +156,16 @@ export const useGalleryHotkeys = () => {
handleRightImage(true);
}
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer' },
dependencies: [isOnLastImageOfView, goNext, isNextEnabled, queryResult.isFetching, handleRightImage, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused || isImageViewerFocused },
dependencies: [
isOnLastImageOfView,
goNext,
isNextEnabled,
queryResult.isFetching,
handleRightImage,
isGalleryFocused,
isImageViewerFocused,
],
});
useRegisteredHotkeys({
@@ -145,8 +178,8 @@ export const useGalleryHotkeys = () => {
}
handleUpImage(true);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused },
dependencies: [handleUpImage, isOnFirstRow, goPrev, isPrevEnabled, queryResult.isFetching, isGalleryFocused],
});
useRegisteredHotkeys({
@@ -159,8 +192,8 @@ export const useGalleryHotkeys = () => {
}
handleDownImage(true);
},
options: { preventDefault: true, enabled: focusedRegion === 'galleryPanel' },
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, focusedRegion],
options: { preventDefault: true, enabled: isGalleryFocused },
dependencies: [isOnLastRow, goNext, isNextEnabled, queryResult.isFetching, handleDownImage, isGalleryFocused],
});
useRegisteredHotkeys({
@@ -173,11 +206,8 @@ export const useGalleryHotkeys = () => {
dispatch(imagesToDeleteSelected(selection));
},
options: {
enabled:
(focusedRegion === 'galleryPanel' || focusedRegion === 'imageViewer') &&
isDeleteEnabledByTab &&
!workflowsFocus.isFocused,
enabled: (isGalleryFocused || isImageViewerFocused) && isDeleteEnabledByTab && !isWorkflowsFocused,
},
dependencies: [focusedRegion, isDeleteEnabledByTab, selection, workflowsFocus],
dependencies: [isWorkflowsFocused, isDeleteEnabledByTab, selection, isWorkflowsFocused],
});
};

View File

@@ -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 { FOCUS_REGIONS, useFocusRegion } from 'common/hooks/interactionScopes';
import { useFocusRegion, useIsRegionFocused } 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,7 +89,7 @@ export const Flow = memo(() => {
const cancelConnection = useReactFlowStore(selectCancelConnection);
const updateNodeInternals = useUpdateNodeInternals();
const store = useAppStore();
const workflowsFocus = useStore(FOCUS_REGIONS.$workflows);
const isWorkflowsFocused = useIsRegionFocused('workflows');
useFocusRegion('workflows', flowWrapper);
useWorkflowWatcher();
@@ -243,8 +243,8 @@ export const Flow = memo(() => {
id: 'selectAll',
category: 'workflows',
callback: selectAll,
options: { enabled: workflowsFocus.isFocused, preventDefault: true },
dependencies: [selectAll, workflowsFocus],
options: { enabled: isWorkflowsFocused, preventDefault: true },
dependencies: [selectAll, isWorkflowsFocused],
});
useRegisteredHotkeys({
@@ -317,8 +317,8 @@ export const Flow = memo(() => {
id: 'deleteSelection',
category: 'workflows',
callback: deleteSelection,
options: { preventDefault: true, enabled: workflowsFocus.isFocused },
dependencies: [deleteSelection, workflowsFocus],
options: { preventDefault: true, enabled: isWorkflowsFocused },
dependencies: [deleteSelection, isWorkflowsFocused],
});
return (