feat(ui): abstract out the global menu close trigger

This logic is moved into a hook.

This is needed for our context menus to close when the user clicks something in reactflow. It needed to be extended to support menus also.
This commit is contained in:
psychedelicious 2023-12-06 18:54:23 +11:00
parent 3423b5848f
commit 61060f032a
7 changed files with 43 additions and 17 deletions

View File

@ -22,7 +22,7 @@ import {
PortalProps, PortalProps,
useEventListener, useEventListener,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppSelector } from 'app/store/storeHooks'; import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
import * as React from 'react'; import * as React from 'react';
import { import {
MutableRefObject, MutableRefObject,
@ -49,10 +49,6 @@ export function IAIContextMenu<T extends HTMLElement = HTMLElement>(
const [position, setPosition] = useState<[number, number]>([0, 0]); const [position, setPosition] = useState<[number, number]>([0, 0]);
const targetRef = useRef<T>(null); const targetRef = useRef<T>(null);
const globalContextMenuCloseTrigger = useAppSelector(
(state) => state.ui.globalContextMenuCloseTrigger
);
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setTimeout(() => { setTimeout(() => {
@ -70,11 +66,12 @@ export function IAIContextMenu<T extends HTMLElement = HTMLElement>(
} }
}, [isOpen]); }, [isOpen]);
useEffect(() => { const onClose = useCallback(() => {
setIsOpen(false); setIsOpen(false);
setIsDeferredOpen(false); setIsDeferredOpen(false);
setIsRendered(false); setIsRendered(false);
}, [globalContextMenuCloseTrigger]); }, []);
useGlobalMenuCloseTrigger(onClose);
useEventListener('contextmenu', (e) => { useEventListener('contextmenu', (e) => {
if ( if (

View File

@ -0,0 +1,25 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useEffect } from 'react';
/**
* 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.
*
* 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
*/
export const useGlobalMenuCloseTrigger = (onClose: () => void) => {
const globalMenuCloseTrigger = useAppSelector(
(state) => state.ui.globalMenuCloseTrigger
);
useEffect(() => {
onClose();
}, [globalMenuCloseTrigger, onClose]);
};

View File

@ -4,7 +4,7 @@ import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { $flow } from 'features/nodes/store/reactFlowInstance'; import { $flow } from 'features/nodes/store/reactFlowInstance';
import { contextMenusClosed } from 'features/ui/store/uiSlice'; import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
import { MouseEvent, useCallback, useRef } from 'react'; import { MouseEvent, useCallback, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { import {
@ -153,7 +153,7 @@ export const Flow = () => {
); );
const handlePaneClick = useCallback(() => { const handlePaneClick = useCallback(() => {
dispatch(contextMenusClosed()); dispatch(bumpGlobalMenuCloseTrigger());
}, [dispatch]); }, [dispatch]);
const onInit: OnInit = useCallback((flow) => { const onInit: OnInit = useCallback((flow) => {

View File

@ -15,7 +15,7 @@ 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 { contextMenusClosed } from 'features/ui/store/uiSlice'; import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
import { import {
MouseEvent, MouseEvent,
PropsWithChildren, PropsWithChildren,
@ -70,7 +70,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
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(contextMenusClosed()); dispatch(bumpGlobalMenuCloseTrigger());
}, },
[dispatch, nodeId] [dispatch, nodeId]
); );

View File

@ -6,6 +6,7 @@ import {
MenuItem, MenuItem,
MenuList, MenuList,
Spacer, Spacer,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { memo } from 'react'; import { memo } from 'react';
@ -24,9 +25,12 @@ import HotkeysModal from './HotkeysModal/HotkeysModal';
import InvokeAILogoComponent from './InvokeAILogoComponent'; import InvokeAILogoComponent from './InvokeAILogoComponent';
import SettingsModal from './SettingsModal/SettingsModal'; import SettingsModal from './SettingsModal/SettingsModal';
import StatusIndicator from './StatusIndicator'; import StatusIndicator from './StatusIndicator';
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
const SiteHeader = () => { const SiteHeader = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
useGlobalMenuCloseTrigger(onClose);
const isBugLinkEnabled = useFeatureStatus('bugLink').isFeatureEnabled; const isBugLinkEnabled = useFeatureStatus('bugLink').isFeatureEnabled;
const isDiscordLinkEnabled = useFeatureStatus('discordLink').isFeatureEnabled; const isDiscordLinkEnabled = useFeatureStatus('discordLink').isFeatureEnabled;
@ -46,7 +50,7 @@ const SiteHeader = () => {
<Spacer /> <Spacer />
<StatusIndicator /> <StatusIndicator />
<Menu> <Menu isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
<MenuButton <MenuButton
as={IAIIconButton} as={IAIIconButton}
variant="link" variant="link"

View File

@ -16,7 +16,7 @@ export const initialUIState: UIState = {
shouldShowEmbeddingPicker: false, shouldShowEmbeddingPicker: false,
shouldAutoChangeDimensions: false, shouldAutoChangeDimensions: false,
favoriteSchedulers: [], favoriteSchedulers: [],
globalContextMenuCloseTrigger: 0, globalMenuCloseTrigger: 0,
panels: {}, panels: {},
}; };
@ -60,8 +60,8 @@ export const uiSlice = createSlice({
setShouldAutoChangeDimensions: (state, action: PayloadAction<boolean>) => { setShouldAutoChangeDimensions: (state, action: PayloadAction<boolean>) => {
state.shouldAutoChangeDimensions = action.payload; state.shouldAutoChangeDimensions = action.payload;
}, },
contextMenusClosed: (state) => { bumpGlobalMenuCloseTrigger: (state) => {
state.globalContextMenuCloseTrigger += 1; state.globalMenuCloseTrigger += 1;
}, },
panelsChanged: ( panelsChanged: (
state, state,
@ -88,7 +88,7 @@ export const {
favoriteSchedulersChanged, favoriteSchedulersChanged,
toggleEmbeddingPicker, toggleEmbeddingPicker,
setShouldAutoChangeDimensions, setShouldAutoChangeDimensions,
contextMenusClosed, bumpGlobalMenuCloseTrigger,
panelsChanged, panelsChanged,
} = uiSlice.actions; } = uiSlice.actions;

View File

@ -24,6 +24,6 @@ export interface UIState {
shouldShowEmbeddingPicker: boolean; shouldShowEmbeddingPicker: boolean;
shouldAutoChangeDimensions: boolean; shouldAutoChangeDimensions: boolean;
favoriteSchedulers: ParameterScheduler[]; favoriteSchedulers: ParameterScheduler[];
globalContextMenuCloseTrigger: number; globalMenuCloseTrigger: number;
panels: Record<string, string>; panels: Record<string, string>;
} }