feat(ui): use pubsub to for globalcontextmenuclose

Far more efficient than the crude redux incrementor thing.
This commit is contained in:
psychedelicious 2023-12-30 23:53:55 +11:00 committed by Kent Keirsey
parent bd92a31d15
commit 2ba505cce9
7 changed files with 37 additions and 32 deletions

View File

@ -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 };
}; };

View File

@ -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);

View File

@ -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 (

View File

@ -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',
];

View File

@ -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;

View File

@ -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>;
} }

View File

@ -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')}