diff --git a/invokeai/frontend/web/.storybook/ReduxInit.tsx b/invokeai/frontend/web/.storybook/ReduxInit.tsx index 9d46432603..9e62877ef0 100644 --- a/invokeai/frontend/web/.storybook/ReduxInit.tsx +++ b/invokeai/frontend/web/.storybook/ReduxInit.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren, memo, useEffect } from 'react'; import { modelChanged } from '../src/features/parameters/store/generationSlice'; import { useAppDispatch } from '../src/app/store/storeHooks'; -import { useGlobalModifiersInit } from '../src/common/hooks/useGlobalModifiers'; +import { useGlobalModifiersInit } from '@invoke-ai/ui'; /** * Initializes some state for storybook. Must be in a different component * so that it is run inside the redux context. diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 6be1741cfa..ecd3c829d1 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -1,4 +1,4 @@ -import { Box } from '@invoke-ai/ui'; +import { Box, useGlobalModifiersInit } from '@invoke-ai/ui'; import { useSocketIO } from 'app/hooks/useSocketIO'; import { useLogger } from 'app/logging/useLogger'; import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted'; @@ -8,7 +8,6 @@ import ImageUploadOverlay from 'common/components/ImageUploadOverlay'; import { useClearStorage } from 'common/hooks/useClearStorage'; import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone'; import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; -import { useGlobalModifiersInit } from 'common/hooks/useGlobalModifiers'; import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal'; import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal'; import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal'; diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalMenuClose.ts b/invokeai/frontend/web/src/common/hooks/useGlobalMenuClose.ts deleted file mode 100644 index e3537b8c60..0000000000 --- a/invokeai/frontend/web/src/common/hooks/useGlobalMenuClose.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { atom } from 'nanostores'; -import { useCallback, useEffect } from 'react'; - -type CB = () => void; - -const $onCloseCallbacks = atom([]); - -/** - * The reactflow background element somehow prevents the chakra `useOutsideClick()` hook from working. - * With a menu open, clicking on the reactflow background element doesn't close the menu. - * - * Reactflow does provide an `onPaneClick` to handle clicks on the background element, but it is not - * straightforward to programatically close all menus. - * - * This hook provides a way to close all menus by calling `onCloseGlobal()`. Menus that want to be closed - * in this way should register themselves by passing a callback to `useGlobalMenuCloseTrigger()`. - */ -export const useGlobalMenuClose = (onClose?: CB) => { - useEffect(() => { - if (!onClose) { - return; - } - $onCloseCallbacks.set([...$onCloseCallbacks.get(), onClose]); - return () => { - $onCloseCallbacks.set( - $onCloseCallbacks.get().filter((c) => c !== onClose) - ); - }; - }, [onClose]); - - const onCloseGlobal = useCallback(() => { - $onCloseCallbacks.get().forEach((cb) => cb()); - }, []); - - return { onCloseGlobal }; -}; diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalModifiers.ts b/invokeai/frontend/web/src/common/hooks/useGlobalModifiers.ts deleted file mode 100644 index 5b5b726978..0000000000 --- a/invokeai/frontend/web/src/common/hooks/useGlobalModifiers.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { atom } from 'nanostores'; -import { useCallback, useEffect } from 'react'; - -export const $shift = atom(false); -export const $ctrl = atom(false); -export const $meta = atom(false); -export const $alt = atom(false); - -const $subscribers = atom(0); - -const listener = (e: KeyboardEvent) => { - $shift.set(e.shiftKey); - $ctrl.set(e.ctrlKey); - $alt.set(e.altKey); - $meta.set(e.metaKey); -}; - -export const useGlobalModifiersInit = () => { - useEffect(() => { - $subscribers.set($subscribers.get() + 1); - - if ($subscribers.get() === 1) { - window.addEventListener('keydown', listener); - window.addEventListener('keyup', listener); - } - - return () => { - $subscribers.set(Math.max($subscribers.get() - 1, 0)); - if ($subscribers.get() > 0) { - return; - } - window.removeEventListener('keydown', listener); - window.removeEventListener('keyup', listener); - }; - }, []); -}; - -export const useGlobalModifiersSetters = () => { - const setShift = useCallback((shift: boolean) => { - $shift.set(shift); - }, []); - const setCtrl = useCallback((ctrl: boolean) => { - $ctrl.set(ctrl); - }, []); - const setAlt = useCallback((alt: boolean) => { - $alt.set(alt); - }, []); - const setMeta = useCallback((meta: boolean) => { - $meta.set(meta); - }, []); - return { setShift, setCtrl, setAlt, setMeta }; -}; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx index 9207242915..bc7bc6fa4c 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx @@ -1,6 +1,6 @@ +import { useShiftModifier } from '@invoke-ai/ui'; import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { $shift } from 'common/hooks/useGlobalModifiers'; import { roundDownToMultiple, roundDownToMultipleMin, @@ -54,7 +54,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => { const optimalDimension = useAppSelector(selectOptimalDimension); const transformerRef = useRef(null); const shapeRef = useRef(null); - const shift = useStore($shift); + const shift = useShiftModifier(); const tool = useStore($tool); const isDrawing = useStore($isDrawing); const isMovingBoundingBox = useStore($isMovingBoundingBox); 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 893c419223..e42b4ca815 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -1,12 +1,11 @@ import type { SystemStyleObject } from '@invoke-ai/ui'; -import { Box, Flex } from '@invoke-ai/ui'; +import { Box, Flex, useShiftModifier } from '@invoke-ai/ui'; import { useStore } from '@nanostores/react'; import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIFillSkeleton from 'common/components/IAIFillSkeleton'; -import { $shift } from 'common/hooks/useGlobalModifiers'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import type { GallerySelectionDraggableData, @@ -40,7 +39,7 @@ const GalleryImage = (props: HoverableImageProps) => { const dispatch = useAppDispatch(); const { imageName } = props; const { currentData: imageDTO } = useGetImageDTOQuery(imageName); - const shift = useStore($shift); + const shift = useShiftModifier() const { t } = useTranslation(); const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const { handleClick, isSelected, areMultiplesSelected } = diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx index 7d88aa1f58..ff69164eb3 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx @@ -1,6 +1,5 @@ -import { useToken } from '@invoke-ai/ui'; +import { useGlobalMenuClose, useToken } from '@invoke-ai/ui'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { useGlobalMenuClose } from 'common/hooks/useGlobalMenuClose'; import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection'; import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode'; import { useWorkflowWatcher } from 'features/nodes/hooks/useWorkflowWatcher'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx index 8556264b71..2a3490d360 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx @@ -1,9 +1,8 @@ import type { ChakraProps } from '@invoke-ai/ui'; -import { Box, useToken } from '@invoke-ai/ui'; +import { Box, useGlobalMenuClose, useToken } from '@invoke-ai/ui'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay'; -import { useGlobalMenuClose } from 'common/hooks/useGlobalMenuClose'; import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode'; import { nodeExclusivelySelected, diff --git a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx index e5b9cccdb7..21e8bedcc4 100644 --- a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx +++ b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx @@ -1,7 +1,5 @@ import type { IconButtonProps } from '@invoke-ai/ui'; -import { IconButton, useDisclosure } from '@invoke-ai/ui'; -import { useStore } from '@nanostores/react'; -import { $shift } from 'common/hooks/useGlobalModifiers'; +import { IconButton, useDisclosure, useShiftModifier } from '@invoke-ai/ui'; import ClearQueueConfirmationAlertDialog from 'features/queue/components/ClearQueueConfirmationAlertDialog'; import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem'; import { useClearQueue } from 'features/queue/hooks/useClearQueue'; @@ -59,7 +57,7 @@ const ClearSingleQueueItemIconButton = (props: ClearQueueButtonProps) => { export const ClearQueueIconButton = (props: ClearQueueButtonProps) => { // Show the single item clear button when shift is pressed // Otherwise show the clear queue button - const shift = useStore($shift); + const shift = useShiftModifier() const disclosure = useDisclosure(); return ( diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx index d744ac2892..965a43690b 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx @@ -6,8 +6,8 @@ import { MenuItem, MenuList, useDisclosure, + useGlobalMenuClose, } from '@invoke-ai/ui'; -import { useGlobalMenuClose } from 'common/hooks/useGlobalMenuClose'; import AboutModal from 'features/system/components/AboutModal/AboutModal'; import HotkeysModal from 'features/system/components/HotkeysModal/HotkeysModal'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx index 2ab728ae1f..b174bf483e 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx @@ -5,8 +5,8 @@ import { MenuDivider, MenuList, useDisclosure, + useGlobalMenuClose, } from '@invoke-ai/ui'; -import { useGlobalMenuClose } from 'common/hooks/useGlobalMenuClose'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import DownloadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/DownloadWorkflowMenuItem'; import NewWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/NewWorkflowMenuItem';