mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use pubsub to for globalcontextmenuclose
Far more efficient than the crude redux incrementor thing.
This commit is contained in:
parent
bd92a31d15
commit
2ba505cce9
@ -1,25 +1,36 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { atom } from 'nanostores';
|
||||||
import { useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
type CB = () => void;
|
||||||
|
|
||||||
|
const $onCloseCallbacks = atom<CB[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reactflow background element somehow prevents the chakra `useOutsideClick()` hook from working.
|
* 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.
|
* 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
|
* 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:
|
* This hook provides a way to close all menus by calling `onCloseGlobal()`. Menus that want to be closed
|
||||||
* - create `globalMenuCloseTrigger: number` in `ui` slice
|
* in this way should register themselves by passing a callback to `useGlobalMenuCloseTrigger()`.
|
||||||
* - increment it in `onPaneClick`
|
|
||||||
* - `useEffect()` to close the menu when `globalMenuCloseTrigger` changes
|
|
||||||
*/
|
*/
|
||||||
|
export const useGlobalMenuCloseTrigger = (onClose?: CB) => {
|
||||||
export const useGlobalMenuCloseTrigger = (onClose: () => void) => {
|
|
||||||
const globalMenuCloseTrigger = useAppSelector(
|
|
||||||
(state) => state.ui.globalMenuCloseTrigger
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onClose();
|
if (!onClose) {
|
||||||
}, [globalMenuCloseTrigger, 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 };
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { useToken } from '@chakra-ui/react';
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
import {
|
import {
|
||||||
connectionEnded,
|
connectionEnded,
|
||||||
@ -22,7 +23,6 @@ import {
|
|||||||
viewportChanged,
|
viewportChanged,
|
||||||
} from 'features/nodes/store/nodesSlice';
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
|
||||||
import type { CSSProperties, MouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent } from 'react';
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
@ -157,9 +157,10 @@ export const Flow = memo(() => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { onCloseGlobal } = useGlobalMenuCloseTrigger();
|
||||||
const handlePaneClick = useCallback(() => {
|
const handlePaneClick = useCallback(() => {
|
||||||
dispatch(bumpGlobalMenuCloseTrigger());
|
onCloseGlobal();
|
||||||
}, [dispatch]);
|
}, [onCloseGlobal]);
|
||||||
|
|
||||||
const onInit: OnInit = useCallback((flow) => {
|
const onInit: OnInit = useCallback((flow) => {
|
||||||
$flow.set(flow);
|
$flow.set(flow);
|
||||||
|
@ -4,6 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
|
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
|
||||||
|
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||||
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||||
import { nodeExclusivelySelected } from 'features/nodes/store/nodesSlice';
|
import { nodeExclusivelySelected } from 'features/nodes/store/nodesSlice';
|
||||||
import {
|
import {
|
||||||
@ -11,7 +12,6 @@ import {
|
|||||||
NODE_WIDTH,
|
NODE_WIDTH,
|
||||||
} from 'features/nodes/types/constants';
|
} from 'features/nodes/types/constants';
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
|
||||||
import type { MouseEvent, PropsWithChildren } from 'react';
|
import type { MouseEvent, PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
@ -48,15 +48,16 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const opacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
const opacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
||||||
|
const { onCloseGlobal } = useGlobalMenuCloseTrigger();
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) {
|
if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) {
|
||||||
dispatch(nodeExclusivelySelected(nodeId));
|
dispatch(nodeExclusivelySelected(nodeId));
|
||||||
}
|
}
|
||||||
dispatch(bumpGlobalMenuCloseTrigger());
|
onCloseGlobal();
|
||||||
},
|
},
|
||||||
[dispatch, nodeId]
|
[dispatch, onCloseGlobal, nodeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3,7 +3,4 @@ import type { UIState } from './uiTypes';
|
|||||||
/**
|
/**
|
||||||
* UI slice persist denylist
|
* UI slice persist denylist
|
||||||
*/
|
*/
|
||||||
export const uiPersistDenylist: (keyof UIState)[] = [
|
export const uiPersistDenylist: (keyof UIState)[] = ['shouldShowImageDetails'];
|
||||||
'shouldShowImageDetails',
|
|
||||||
'globalMenuCloseTrigger',
|
|
||||||
];
|
|
||||||
|
@ -12,7 +12,6 @@ export const initialUIState: UIState = {
|
|||||||
shouldHidePreview: false,
|
shouldHidePreview: false,
|
||||||
shouldShowProgressInViewer: true,
|
shouldShowProgressInViewer: true,
|
||||||
shouldAutoChangeDimensions: false,
|
shouldAutoChangeDimensions: false,
|
||||||
globalMenuCloseTrigger: 0,
|
|
||||||
panels: {},
|
panels: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,9 +40,6 @@ export const uiSlice = createSlice({
|
|||||||
setShouldAutoChangeDimensions: (state, action: PayloadAction<boolean>) => {
|
setShouldAutoChangeDimensions: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldAutoChangeDimensions = action.payload;
|
state.shouldAutoChangeDimensions = action.payload;
|
||||||
},
|
},
|
||||||
bumpGlobalMenuCloseTrigger: (state) => {
|
|
||||||
state.globalMenuCloseTrigger += 1;
|
|
||||||
},
|
|
||||||
panelsChanged: (
|
panelsChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ name: string; value: string }>
|
action: PayloadAction<{ name: string; value: string }>
|
||||||
@ -65,7 +61,6 @@ export const {
|
|||||||
setShouldHidePreview,
|
setShouldHidePreview,
|
||||||
setShouldShowProgressInViewer,
|
setShouldShowProgressInViewer,
|
||||||
setShouldAutoChangeDimensions,
|
setShouldAutoChangeDimensions,
|
||||||
bumpGlobalMenuCloseTrigger,
|
|
||||||
panelsChanged,
|
panelsChanged,
|
||||||
} = uiSlice.actions;
|
} = uiSlice.actions;
|
||||||
|
|
||||||
|
@ -19,6 +19,5 @@ export interface UIState {
|
|||||||
shouldHidePreview: boolean;
|
shouldHidePreview: boolean;
|
||||||
shouldShowProgressInViewer: boolean;
|
shouldShowProgressInViewer: boolean;
|
||||||
shouldAutoChangeDimensions: boolean;
|
shouldAutoChangeDimensions: boolean;
|
||||||
globalMenuCloseTrigger: number;
|
|
||||||
panels: Record<string, string>;
|
panels: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,12 @@ const WorkflowLibraryMenu = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
useGlobalMenuCloseTrigger(onClose);
|
useGlobalMenuCloseTrigger(onClose);
|
||||||
|
|
||||||
const isWorkflowLibraryEnabled =
|
const isWorkflowLibraryEnabled =
|
||||||
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InvMenu isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
|
<InvMenu isOpen={isOpen} onOpen={onOpen} onClose={onClose} isLazy>
|
||||||
<InvMenuButton
|
<InvMenuButton
|
||||||
as={InvIconButton}
|
as={InvIconButton}
|
||||||
aria-label={t('workflows.workflowEditorMenu')}
|
aria-label={t('workflows.workflowEditorMenu')}
|
||||||
|
Loading…
Reference in New Issue
Block a user