diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalMenuCloseTrigger.ts b/invokeai/frontend/web/src/common/hooks/useGlobalMenuCloseTrigger.ts index 0fd404fdc3..681be9ef08 100644 --- a/invokeai/frontend/web/src/common/hooks/useGlobalMenuCloseTrigger.ts +++ b/invokeai/frontend/web/src/common/hooks/useGlobalMenuCloseTrigger.ts @@ -1,25 +1,36 @@ -import { useAppSelector } from 'app/store/storeHooks'; -import { useEffect } from 'react'; +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 the menu. + * straightforward to programatically close all menus. * - * As a (hopefully temporary) workaround, we will use a dirty hack: - * - create `globalMenuCloseTrigger: number` in `ui` slice - * - increment it in `onPaneClick` - * - `useEffect()` to close the menu when `globalMenuCloseTrigger` changes + * 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 useGlobalMenuCloseTrigger = (onClose: () => void) => { - const globalMenuCloseTrigger = useAppSelector( - (state) => state.ui.globalMenuCloseTrigger - ); - +export const useGlobalMenuCloseTrigger = (onClose?: CB) => { useEffect(() => { - onClose(); - }, [globalMenuCloseTrigger, onClose]); + 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/features/nodes/components/flow/Flow.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx index 1470746470..a560008aba 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx @@ -2,6 +2,7 @@ import { useToken } from '@chakra-ui/react'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger'; import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection'; import { connectionEnded, @@ -22,7 +23,6 @@ import { viewportChanged, } from 'features/nodes/store/nodesSlice'; import { $flow } from 'features/nodes/store/reactFlowInstance'; -import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice'; import type { CSSProperties, MouseEvent } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -157,9 +157,10 @@ export const Flow = memo(() => { [dispatch] ); + const { onCloseGlobal } = useGlobalMenuCloseTrigger(); const handlePaneClick = useCallback(() => { - dispatch(bumpGlobalMenuCloseTrigger()); - }, [dispatch]); + onCloseGlobal(); + }, [onCloseGlobal]); const onInit: OnInit = useCallback((flow) => { $flow.set(flow); 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 e81ba17187..754b404237 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 @@ -4,6 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay'; +import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger'; import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode'; import { nodeExclusivelySelected } from 'features/nodes/store/nodesSlice'; import { @@ -11,7 +12,6 @@ import { NODE_WIDTH, } from 'features/nodes/types/constants'; import { zNodeStatus } from 'features/nodes/types/invocation'; -import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice'; import type { MouseEvent, PropsWithChildren } from 'react'; import { memo, useCallback, useMemo } from 'react'; @@ -48,15 +48,16 @@ const NodeWrapper = (props: NodeWrapperProps) => { const dispatch = useAppDispatch(); const opacity = useAppSelector((state) => state.nodes.nodeOpacity); + const { onCloseGlobal } = useGlobalMenuCloseTrigger(); const handleClick = useCallback( (e: MouseEvent) => { if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) { dispatch(nodeExclusivelySelected(nodeId)); } - dispatch(bumpGlobalMenuCloseTrigger()); + onCloseGlobal(); }, - [dispatch, nodeId] + [dispatch, onCloseGlobal, nodeId] ); return ( diff --git a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts index f6fe56df0b..815f8dfca5 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts @@ -3,7 +3,4 @@ import type { UIState } from './uiTypes'; /** * UI slice persist denylist */ -export const uiPersistDenylist: (keyof UIState)[] = [ - 'shouldShowImageDetails', - 'globalMenuCloseTrigger', -]; +export const uiPersistDenylist: (keyof UIState)[] = ['shouldShowImageDetails']; diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 44634b67d7..3978808666 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -12,7 +12,6 @@ export const initialUIState: UIState = { shouldHidePreview: false, shouldShowProgressInViewer: true, shouldAutoChangeDimensions: false, - globalMenuCloseTrigger: 0, panels: {}, }; @@ -41,9 +40,6 @@ export const uiSlice = createSlice({ setShouldAutoChangeDimensions: (state, action: PayloadAction) => { state.shouldAutoChangeDimensions = action.payload; }, - bumpGlobalMenuCloseTrigger: (state) => { - state.globalMenuCloseTrigger += 1; - }, panelsChanged: ( state, action: PayloadAction<{ name: string; value: string }> @@ -65,7 +61,6 @@ export const { setShouldHidePreview, setShouldShowProgressInViewer, setShouldAutoChangeDimensions, - bumpGlobalMenuCloseTrigger, panelsChanged, } = uiSlice.actions; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index c560641828..7dbd77b995 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -19,6 +19,5 @@ export interface UIState { shouldHidePreview: boolean; shouldShowProgressInViewer: boolean; shouldAutoChangeDimensions: boolean; - globalMenuCloseTrigger: number; panels: Record; } 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 a32f29edab..d76a4e7fdd 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu.tsx @@ -22,11 +22,12 @@ const WorkflowLibraryMenu = () => { const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); useGlobalMenuCloseTrigger(onClose); + const isWorkflowLibraryEnabled = useFeatureStatus('workflowLibrary').isFeatureEnabled; return ( - +