9426 migrate workflow pages to command menu (#9515)

Closes twentyhq/core-team-issues#53 

- Removes command menu top bar text input when the user is not on root
page
- Fixes bug when resetting command menu context
- Added animations on command menu open and close
- Refactored workflow visualizer code to remove unnecessary rerenders
and props drilling


https://github.com/user-attachments/assets/1da3adb8-220b-407b-9279-30354d3100d3
This commit is contained in:
Raphaël Bosi
2025-01-13 16:53:57 +01:00
committed by GitHub
parent 330addbc0b
commit 530a18558b
22 changed files with 328 additions and 168 deletions

View File

@ -1,12 +1,14 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer'; import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState'; import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
export const useEmailThread = () => { export const useEmailThread = () => {
const { closeRightDrawer } = useRightDrawer(); const { closeRightDrawer } = useRightDrawer();
const { closeCommandMenu } = useCommandMenu();
const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer(); const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer();
const openEmailThread = useRecoilCallback( const openEmailThread = useRecoilCallback(
@ -23,13 +25,14 @@ export const useEmailThread = () => {
if (isRightDrawerOpen && viewableEmailThreadId === threadId) { if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
set(viewableRecordIdState, null); set(viewableRecordIdState, null);
closeRightDrawer(); closeRightDrawer();
closeCommandMenu();
return; return;
} }
openEmailThreadRightDrawer(); openEmailThreadRightDrawer();
set(viewableRecordIdState, threadId); set(viewableRecordIdState, threadId);
}, },
[closeRightDrawer, openEmailThreadRightDrawer], [closeRightDrawer, closeCommandMenu, openEmailThreadRightDrawer],
); );
return { openEmailThread }; return { openEmailThread };

View File

@ -3,20 +3,25 @@ import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys'; import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRef } from 'react'; import { useRef } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useIsMobile } from 'twenty-ui'; import { useIsMobile } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
const StyledCommandMenu = styled.div` const StyledCommandMenu = styled(motion.div)`
background: ${({ theme }) => theme.background.secondary}; background: ${({ theme }) => theme.background.secondary};
border-left: 1px solid ${({ theme }) => theme.border.color.medium}; border-left: 1px solid ${({ theme }) => theme.border.color.medium};
box-shadow: ${({ theme }) => theme.boxShadow.strong}; box-shadow: ${({ theme }) => theme.boxShadow.strong};
@ -27,8 +32,9 @@ const StyledCommandMenu = styled.div`
position: fixed; position: fixed;
right: 0%; right: 0%;
top: 0%; top: 0%;
width: ${() => (useIsMobile() ? '100%' : '500px')};
z-index: 30; z-index: 30;
display: flex;
flex-direction: column;
`; `;
export const CommandMenuContainer = ({ export const CommandMenuContainer = ({
@ -45,15 +51,28 @@ export const CommandMenuContainer = ({
const commandMenuRef = useRef<HTMLDivElement>(null); const commandMenuRef = useRef<HTMLDivElement>(null);
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
useCommandMenuHotKeys(); useCommandMenuHotKeys();
useListenClickOutside({ useListenClickOutside({
refs: [commandMenuRef], refs: [
commandMenuRef,
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
],
callback: closeCommandMenu, callback: closeCommandMenu,
listenerId: 'COMMAND_MENU_LISTENER_ID', listenerId: 'COMMAND_MENU_LISTENER_ID',
hotkeyScope: AppHotkeyScope.CommandMenuOpen, hotkeyScope: AppHotkeyScope.CommandMenuOpen,
}); });
const isMobile = useIsMobile();
const targetVariantForAnimation: CommandMenuAnimationVariant = isMobile
? 'fullScreen'
: 'normal';
const theme = useTheme();
return ( return (
<ContextStoreComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }} value={{ instanceId: 'command-menu' }}
@ -71,7 +90,17 @@ export const CommandMenuContainer = ({
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />} {isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
<ActionMenuConfirmationModals /> <ActionMenuConfirmationModals />
{isCommandMenuOpened && ( {isCommandMenuOpened && (
<StyledCommandMenu ref={commandMenuRef} className="command-menu"> <StyledCommandMenu
ref={commandMenuRef}
className="command-menu"
animate={targetVariantForAnimation}
initial="closed"
exit="closed"
variants={COMMAND_MENU_ANIMATION_VARIANTS}
transition={{
duration: theme.animation.duration.normal,
}}
>
{children} {children}
</StyledCommandMenu> </StyledCommandMenu>
)} )}

View File

@ -3,4 +3,8 @@ export enum CommandMenuPages {
ViewRecord = 'view-record', ViewRecord = 'view-record',
ViewEmailThread = 'view-email-thread', ViewEmailThread = 'view-email-thread',
ViewCalendarEvent = 'view-calendar-event', ViewCalendarEvent = 'view-calendar-event',
WorkflowStepSelectTriggerType = 'workflow-step-select-trigger-type',
WorkflowStepSelectAction = 'workflow-step-select-action',
WorkflowStepView = 'workflow-step-view',
WorkflowStepEdit = 'workflow-step-edit',
} }

View File

@ -2,9 +2,15 @@ import { CommandMenuContainer } from '@/command-menu/components/CommandMenuConta
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar'; import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig'; import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
const StyledCommandMenuContent = styled.div`
flex: 1;
overflow-y: auto;
`;
export const CommandMenuRouter = () => { export const CommandMenuRouter = () => {
const commandMenuPage = useRecoilValue(commandMenuPageState); const commandMenuPage = useRecoilValue(commandMenuPageState);
@ -17,7 +23,9 @@ export const CommandMenuRouter = () => {
return ( return (
<CommandMenuContainer> <CommandMenuContainer>
<CommandMenuTopBar /> <CommandMenuTopBar />
{commandMenuPageComponent} <StyledCommandMenuContent>
{commandMenuPageComponent}
</StyledCommandMenuContent>
</CommandMenuContainer> </CommandMenuContainer>
); );
}; };

View File

@ -1,12 +1,14 @@
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip'; import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight'; import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding'; import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui'; import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui';
const StyledInputContainer = styled.div` const StyledInputContainer = styled.div`
@ -17,6 +19,7 @@ const StyledInputContainer = styled.div`
border-radius: 0; border-radius: 0;
display: flex; display: flex;
justify-content: space-between;
font-size: ${({ theme }) => theme.font.size.lg}; font-size: ${({ theme }) => theme.font.size.lg};
height: ${COMMAND_MENU_SEARCH_BAR_HEIGHT}px; height: ${COMMAND_MENU_SEARCH_BAR_HEIGHT}px;
margin: 0; margin: 0;
@ -25,6 +28,7 @@ const StyledInputContainer = styled.div`
padding: 0 ${({ theme }) => theme.spacing(COMMAND_MENU_SEARCH_BAR_PADDING)}; padding: 0 ${({ theme }) => theme.spacing(COMMAND_MENU_SEARCH_BAR_PADDING)};
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
flex-shrink: 0;
`; `;
const StyledInput = styled.input` const StyledInput = styled.input`
@ -45,6 +49,13 @@ const StyledInput = styled.input`
} }
`; `;
const StyledContentContainer = styled.div`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledCloseButtonContainer = styled.div` const StyledCloseButtonContainer = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
@ -69,19 +80,25 @@ export const CommandMenuTopBar = () => {
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataIdComponentState,
); );
const commandMenuPage = useRecoilValue(commandMenuPageState);
return ( return (
<StyledInputContainer> <StyledInputContainer>
{isDefined(contextStoreCurrentObjectMetadataId) && ( <StyledContentContainer>
<CommandMenuContextRecordChip {isDefined(contextStoreCurrentObjectMetadataId) && (
objectMetadataItemId={contextStoreCurrentObjectMetadataId} <CommandMenuContextRecordChip
/> objectMetadataItemId={contextStoreCurrentObjectMetadataId}
)} />
<StyledInput )}
autoFocus {commandMenuPage === CommandMenuPages.Root && (
value={commandMenuSearch} <StyledInput
placeholder="Type anything" autoFocus
onChange={handleSearchChange} value={commandMenuSearch}
/> placeholder="Type anything"
onChange={handleSearchChange}
/>
)}
</StyledContentContainer>
{!isMobile && ( {!isMobile && (
<StyledCloseButtonContainer> <StyledCloseButtonContainer>
<LightIconButton <LightIconButton

View File

@ -0,0 +1,25 @@
import { THEME_COMMON } from 'twenty-ui';
export const COMMAND_MENU_ANIMATION_VARIANTS = {
fullScreen: {
x: '0%',
width: '100%',
height: '100%',
bottom: '0',
top: '0',
},
normal: {
x: '0%',
width: THEME_COMMON.rightDrawerWidth,
height: '100%',
bottom: '0',
top: '0',
},
closed: {
x: '100%',
width: THEME_COMMON.rightDrawerWidth,
height: '100%',
bottom: '0',
top: 'auto',
},
};

View File

@ -3,6 +3,10 @@ import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/compone
import { CommandMenu } from '@/command-menu/components/CommandMenu'; import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord'; import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
export const COMMAND_MENU_PAGES_CONFIG = new Map< export const COMMAND_MENU_PAGES_CONFIG = new Map<
CommandMenuPages, CommandMenuPages,
@ -12,4 +16,14 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
[CommandMenuPages.ViewRecord, <RightDrawerRecord />], [CommandMenuPages.ViewRecord, <RightDrawerRecord />],
[CommandMenuPages.ViewEmailThread, <RightDrawerEmailThread />], [CommandMenuPages.ViewEmailThread, <RightDrawerEmailThread />],
[CommandMenuPages.ViewCalendarEvent, <RightDrawerCalendarEvent />], [CommandMenuPages.ViewCalendarEvent, <RightDrawerCalendarEvent />],
[
CommandMenuPages.WorkflowStepSelectTriggerType,
<RightDrawerWorkflowSelectTriggerType />,
],
[
CommandMenuPages.WorkflowStepSelectAction,
<RightDrawerWorkflowSelectAction />,
],
[CommandMenuPages.WorkflowStepEdit, <RightDrawerWorkflowEditStep />],
[CommandMenuPages.WorkflowStepView, <RightDrawerWorkflowViewStep />],
]); ]);

View File

@ -1,4 +1,4 @@
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilValue } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@ -17,10 +17,10 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => { export const useCommandMenu = () => {
const setIsCommandMenuOpened = useSetRecoilState(isCommandMenuOpenedState);
const { resetSelectedItem } = useSelectableList('command-menu-list'); const { resetSelectedItem } = useSelectableList('command-menu-list');
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
@ -141,13 +141,12 @@ export const useCommandMenu = () => {
actionMenuEntries, actionMenuEntries,
); );
setIsCommandMenuOpened(true); set(isCommandMenuOpenedState, true);
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen); setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
}, },
[ [
mainContextStoreComponentInstanceId, mainContextStoreComponentInstanceId,
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
setIsCommandMenuOpened,
], ],
); );
@ -158,67 +157,69 @@ export const useCommandMenu = () => {
.getLoadable(isCommandMenuOpenedState) .getLoadable(isCommandMenuOpenedState)
.getValue(); .getValue();
set(
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'command-menu',
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'command-menu',
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: 'command-menu',
}),
[],
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'command-menu',
}),
new Map(),
);
if (isCommandMenuOpened) { if (isCommandMenuOpened) {
set(
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'command-menu',
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'command-menu',
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: 'command-menu',
}),
[],
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'command-menu',
}),
new Map(),
);
set(viewableRecordIdState, null); set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root); set(commandMenuPageState, CommandMenuPages.Root);
setIsCommandMenuOpened(false); set(isCommandMenuOpenedState, false);
resetSelectedItem(); resetSelectedItem();
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
emitRightDrawerCloseEvent();
} }
}, },
[goBackToPreviousHotkeyScope, resetSelectedItem, setIsCommandMenuOpened], [goBackToPreviousHotkeyScope, resetSelectedItem],
); );
const toggleCommandMenu = useRecoilCallback( const toggleCommandMenu = useRecoilCallback(
@ -250,10 +251,39 @@ export const useCommandMenu = () => {
[openCommandMenu], [openCommandMenu],
); );
const setGlobalCommandMenuContext = useRecoilCallback(({ set }) => {
return () => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'command-menu',
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: 'command-menu',
}),
0,
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: 'command-menu',
}),
null,
);
};
}, []);
return { return {
openCommandMenu, openCommandMenu,
closeCommandMenu, closeCommandMenu,
openRecordInCommandMenu, openRecordInCommandMenu,
toggleCommandMenu, toggleCommandMenu,
resetCommandMenuContext: setGlobalCommandMenuContext,
}; };
}; };

View File

@ -1,32 +1,24 @@
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
export const useCommandMenuHotKeys = () => { export const useCommandMenuHotKeys = () => {
const { closeCommandMenu, toggleCommandMenu } = useCommandMenu(); const { closeCommandMenu, toggleCommandMenu, resetCommandMenuContext } =
useCommandMenu();
const commandMenuSearch = useRecoilValue(commandMenuSearchState); const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
contextStoreTargetedRecordsRuleComponentState,
'command-menu',
);
const setContextStoreNumberOfSelectedRecords = useSetRecoilComponentStateV2(
contextStoreNumberOfSelectedRecordsComponentState,
'command-menu',
);
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu(); const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
useScopedHotkeys( useScopedHotkeys(
'ctrl+k,meta+k', 'ctrl+k,meta+k',
() => { () => {
@ -49,13 +41,11 @@ export const useCommandMenuHotKeys = () => {
useScopedHotkeys( useScopedHotkeys(
[Key.Backspace, Key.Delete], [Key.Backspace, Key.Delete],
() => { () => {
if (!isNonEmptyString(commandMenuSearch)) { if (
setContextStoreTargetedRecordsRule({ commandMenuPage === CommandMenuPages.Root &&
mode: 'selection', !isNonEmptyString(commandMenuSearch)
selectedRecordIds: [], ) {
}); resetCommandMenuContext();
setContextStoreNumberOfSelectedRecords(0);
} }
}, },
AppHotkeyScope.CommandMenuOpen, AppHotkeyScope.CommandMenuOpen,

View File

@ -0,0 +1,4 @@
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
export type CommandMenuAnimationVariant =
keyof typeof COMMAND_MENU_ANIMATION_VARIANTS;

View File

@ -11,6 +11,14 @@ export const mapRightDrawerPageToCommandMenuPage = (
return CommandMenuPages.ViewEmailThread; return CommandMenuPages.ViewEmailThread;
case RightDrawerPages.ViewCalendarEvent: case RightDrawerPages.ViewCalendarEvent:
return CommandMenuPages.ViewCalendarEvent; return CommandMenuPages.ViewCalendarEvent;
case RightDrawerPages.WorkflowStepSelectTriggerType:
return CommandMenuPages.WorkflowStepSelectTriggerType;
case RightDrawerPages.WorkflowStepSelectAction:
return CommandMenuPages.WorkflowStepSelectAction;
case RightDrawerPages.WorkflowStepView:
return CommandMenuPages.WorkflowStepView;
case RightDrawerPages.WorkflowStepEdit:
return CommandMenuPages.WorkflowStepEdit;
default: default:
return CommandMenuPages.Root; return CommandMenuPages.Root;
} }

View File

@ -34,13 +34,18 @@ export const useSelectableList = (selectableListId?: string) => {
); );
const setSelectedItemId = useRecoilCallback( const setSelectedItemId = useRecoilCallback(
({ set }) => ({ set, snapshot }) =>
(itemId: string) => { (itemId: string) => {
resetSelectedItem(); const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState);
if (isDefined(selectedItemId)) {
set(isSelectedItemIdSelector(selectedItemId), false);
}
set(selectedItemIdState, itemId); set(selectedItemIdState, itemId);
set(isSelectedItemIdSelector(itemId), true); set(isSelectedItemIdSelector(itemId), true);
}, },
[resetSelectedItem, selectedItemIdState, isSelectedItemIdSelector], [selectedItemIdState, isSelectedItemIdSelector],
); );
return { return {

View File

@ -1,13 +1,11 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { WorkflowVersionStatus } from '@/workflow/types/Workflow'; import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
import { WorkflowVersionStatusTag } from '@/workflow/workflow-diagram/components/WorkflowVersionStatusTag'; import { WorkflowVersionStatusTag } from '@/workflow/workflow-diagram/components/WorkflowVersionStatusTag';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState'; import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState'; import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { import {
WorkflowDiagram,
WorkflowDiagramEdge, WorkflowDiagramEdge,
WorkflowDiagramNode, WorkflowDiagramNode,
WorkflowDiagramNodeType, WorkflowDiagramNodeType,
@ -16,21 +14,21 @@ import { getOrganizedDiagram } from '@/workflow/workflow-diagram/utils/getOrgani
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import {
applyEdgeChanges,
applyNodeChanges,
Background, Background,
EdgeChange, EdgeChange,
FitViewOptions, FitViewOptions,
getNodesBounds,
NodeChange, NodeChange,
NodeProps, NodeProps,
ReactFlow, ReactFlow,
applyEdgeChanges,
applyNodeChanges,
getNodesBounds,
useReactFlow, useReactFlow,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined, THEME_COMMON } from 'twenty-ui'; import { THEME_COMMON, isDefined } from 'twenty-ui';
const StyledResetReactflowStyles = styled.div` const StyledResetReactflowStyles = styled.div`
height: 100%; height: 100%;
@ -68,12 +66,10 @@ const defaultFitViewOptions = {
} satisfies FitViewOptions; } satisfies FitViewOptions;
export const WorkflowDiagramCanvasBase = ({ export const WorkflowDiagramCanvasBase = ({
diagram,
status, status,
nodeTypes, nodeTypes,
children, children,
}: { }: {
diagram: WorkflowDiagram;
status: WorkflowVersionStatus; status: WorkflowVersionStatus;
nodeTypes: Partial< nodeTypes: Partial<
Record< Record<
@ -95,22 +91,17 @@ export const WorkflowDiagramCanvasBase = ({
workflowReactFlowRefState, workflowReactFlowRefState,
); );
const workflowDiagram = useRecoilValue(workflowDiagramState);
const { nodes, edges } = useMemo( const { nodes, edges } = useMemo(
() => getOrganizedDiagram(diagram), () =>
[diagram], isDefined(workflowDiagram)
? getOrganizedDiagram(workflowDiagram)
: { nodes: [], edges: [] },
[workflowDiagram],
); );
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState); const { rightDrawerState } = useRightDrawerState();
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
const isMobile = useIsMobile();
const rightDrawerState = !isRightDrawerOpen
? 'closed'
: isRightDrawerMinimized
? 'minimized'
: isMobile
? 'fullScreen'
: 'normal';
const rightDrawerWidth = Number( const rightDrawerWidth = Number(
THEME_COMMON.rightDrawerWidth.replace('px', ''), THEME_COMMON.rightDrawerWidth.replace('px', ''),
@ -187,6 +178,8 @@ export const WorkflowDiagramCanvasBase = ({
); );
}, [reactflow, rightDrawerState, rightDrawerWidth]); }, [reactflow, rightDrawerState, rightDrawerWidth]);
const { closeCommandMenu } = useCommandMenu();
return ( return (
<StyledResetReactflowStyles ref={containerRef}> <StyledResetReactflowStyles ref={containerRef}>
<ReactFlow <ReactFlow
@ -220,6 +213,7 @@ export const WorkflowDiagramCanvasBase = ({
nodesFocusable={false} nodesFocusable={false}
edgesFocusable={false} edgesFocusable={false}
nodesDraggable={false} nodesDraggable={false}
onPaneClick={closeCommandMenu}
paneClickDistance={10} // Fix small unwanted user dragging does not select node paneClickDistance={10} // Fix small unwanted user dragging does not select node
> >
<Background color={theme.border.color.medium} size={2} /> <Background color={theme.border.color.medium} size={2} />

View File

@ -4,29 +4,24 @@ import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/workflow-diagram
import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode'; import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode';
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger'; import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable'; import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable';
import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { ReactFlowProvider } from '@xyflow/react'; import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowDiagramCanvasEditable = ({ export const WorkflowDiagramCanvasEditable = ({
diagram,
workflowWithCurrentVersion, workflowWithCurrentVersion,
}: { }: {
diagram: WorkflowDiagram;
workflowWithCurrentVersion: WorkflowWithCurrentVersion; workflowWithCurrentVersion: WorkflowWithCurrentVersion;
}) => { }) => {
return ( return (
<ReactFlowProvider> <ReactFlowProvider>
<WorkflowDiagramCanvasBase <WorkflowDiagramCanvasBase
diagram={diagram}
status={workflowWithCurrentVersion.currentVersion.status} status={workflowWithCurrentVersion.currentVersion.status}
nodeTypes={{ nodeTypes={{
default: WorkflowDiagramStepNodeEditable, default: WorkflowDiagramStepNodeEditable,
'create-step': WorkflowDiagramCreateStepNode, 'create-step': WorkflowDiagramCreateStepNode,
'empty-trigger': WorkflowDiagramEmptyTrigger, 'empty-trigger': WorkflowDiagramEmptyTrigger,
}} }}
> />
<WorkflowDiagramCanvasEditableEffect /> <WorkflowDiagramCanvasEditableEffect />
</WorkflowDiagramCanvasBase>
</ReactFlowProvider> </ReactFlowProvider>
); );
}; };

View File

@ -1,3 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope'; import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
@ -17,6 +18,8 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const { startNodeCreation } = useStartNodeCreation(); const { startNodeCreation } = useStartNodeCreation();
const { openRightDrawer, closeRightDrawer } = useRightDrawer(); const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const { closeCommandMenu } = useCommandMenu();
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
@ -28,7 +31,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
if (isClosingStep) { if (isClosingStep) {
closeRightDrawer(); closeRightDrawer();
closeCommandMenu();
return; return;
} }
@ -55,10 +58,11 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
openRightDrawer(RightDrawerPages.WorkflowStepEdit); openRightDrawer(RightDrawerPages.WorkflowStepEdit);
}, },
[ [
setHotkeyScope,
closeRightDrawer,
openRightDrawer,
setWorkflowSelectedNode, setWorkflowSelectedNode,
setHotkeyScope,
openRightDrawer,
closeRightDrawer,
closeCommandMenu,
startNodeCreation, startNodeCreation,
], ],
); );

View File

@ -3,28 +3,23 @@ import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/component
import { WorkflowDiagramCanvasReadonlyEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect'; import { WorkflowDiagramCanvasReadonlyEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect';
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger'; import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly'; import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
import { WorkflowDiagram } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { ReactFlowProvider } from '@xyflow/react'; import { ReactFlowProvider } from '@xyflow/react';
export const WorkflowDiagramCanvasReadonly = ({ export const WorkflowDiagramCanvasReadonly = ({
diagram,
workflowVersion, workflowVersion,
}: { }: {
diagram: WorkflowDiagram;
workflowVersion: WorkflowVersion; workflowVersion: WorkflowVersion;
}) => { }) => {
return ( return (
<ReactFlowProvider> <ReactFlowProvider>
<WorkflowDiagramCanvasBase <WorkflowDiagramCanvasBase
diagram={diagram}
status={workflowVersion.status} status={workflowVersion.status}
nodeTypes={{ nodeTypes={{
default: WorkflowDiagramStepNodeReadonly, default: WorkflowDiagramStepNodeReadonly,
'empty-trigger': WorkflowDiagramEmptyTrigger, 'empty-trigger': WorkflowDiagramEmptyTrigger,
}} }}
> />
<WorkflowDiagramCanvasReadonlyEffect /> <WorkflowDiagramCanvasReadonlyEffect />
</WorkflowDiagramCanvasBase>
</ReactFlowProvider> </ReactFlowProvider>
); );
}; };

View File

@ -1,3 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope'; import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
@ -14,6 +15,7 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { openRightDrawer, closeRightDrawer } = useRightDrawer(); const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const { closeCommandMenu } = useCommandMenu();
const handleSelectionChange = useCallback( const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => { ({ nodes }: OnSelectionChangeParams) => {
@ -22,7 +24,7 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
if (isClosingStep) { if (isClosingStep) {
closeRightDrawer(); closeRightDrawer();
closeCommandMenu();
return; return;
} }
@ -31,10 +33,11 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
openRightDrawer(RightDrawerPages.WorkflowStepView); openRightDrawer(RightDrawerPages.WorkflowStepView);
}, },
[ [
closeRightDrawer,
openRightDrawer,
setWorkflowSelectedNode, setWorkflowSelectedNode,
setHotkeyScope, setHotkeyScope,
openRightDrawer,
closeRightDrawer,
closeCommandMenu,
], ],
); );

View File

@ -1,8 +1,6 @@
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
import { WorkflowDiagramCanvasReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly'; import { WorkflowDiagramCanvasReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonly';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
export const WorkflowVersionVisualizer = ({ export const WorkflowVersionVisualizer = ({
@ -12,12 +10,7 @@ export const WorkflowVersionVisualizer = ({
}) => { }) => {
const workflowVersion = useWorkflowVersion(workflowVersionId); const workflowVersion = useWorkflowVersion(workflowVersionId);
const workflowDiagram = useRecoilValue(workflowDiagramState); return isDefined(workflowVersion) ? (
<WorkflowDiagramCanvasReadonly workflowVersion={workflowVersion} />
return isDefined(workflowDiagram) && isDefined(workflowVersion) ? (
<WorkflowDiagramCanvasReadonly
diagram={workflowDiagram}
workflowVersion={workflowVersion}
/>
) : null; ) : null;
}; };

View File

@ -2,9 +2,7 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { WorkflowDiagramCanvasEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable'; import { WorkflowDiagramCanvasEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable';
import { WorkflowDiagramEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramEffect'; import { WorkflowDiagramEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramEffect';
import { workflowDiagramState } from '@/workflow/workflow-diagram/states/workflowDiagramState';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
export const WorkflowVisualizer = ({ export const WorkflowVisualizer = ({
@ -15,7 +13,6 @@ export const WorkflowVisualizer = ({
const workflowId = targetableObject.id; const workflowId = targetableObject.id;
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
const workflowDiagram = useRecoilValue(workflowDiagramState);
return ( return (
<> <>
@ -23,9 +20,8 @@ export const WorkflowVisualizer = ({
workflowWithCurrentVersion={workflowWithCurrentVersion} workflowWithCurrentVersion={workflowWithCurrentVersion}
/> />
{isDefined(workflowDiagram) && isDefined(workflowWithCurrentVersion) ? ( {isDefined(workflowWithCurrentVersion) ? (
<WorkflowDiagramCanvasEditable <WorkflowDiagramCanvasEditable
diagram={workflowDiagram}
workflowWithCurrentVersion={workflowWithCurrentVersion} workflowWithCurrentVersion={workflowWithCurrentVersion}
/> />
) : null} ) : null}

View File

@ -0,0 +1,43 @@
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
import { RightDrawerAnimationVariant } from '@/ui/layout/right-drawer/types/RightDrawerAnimationVariant';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { useIsMobile } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRightDrawerState = (): {
rightDrawerState: RightDrawerAnimationVariant | CommandMenuAnimationVariant;
} => {
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
const isMobile = useIsMobile();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
if (isMobile) {
return {
rightDrawerState: 'fullScreen',
};
}
if (isCommandMenuV2Enabled) {
return {
rightDrawerState: isCommandMenuOpened ? 'normal' : 'closed',
};
}
return {
rightDrawerState: !isRightDrawerOpen
? 'closed'
: isRightDrawerMinimized
? 'minimized'
: 'normal',
};
};

View File

@ -1,6 +1,7 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep'; import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
const mockCloseRightDrawer = jest.fn(); const mockCloseRightDrawer = jest.fn();
const mockCreateNewWorkflowVersion = jest.fn(); const mockCreateNewWorkflowVersion = jest.fn();
@ -13,12 +14,6 @@ jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({
}), }),
})); }));
jest.mock('recoil', () => ({
useRecoilValue: () => 'parent-step-id',
useSetRecoilState: () => jest.fn(),
atom: (params: any) => params,
}));
jest.mock('@/ui/layout/right-drawer/hooks/useRightDrawer', () => ({ jest.mock('@/ui/layout/right-drawer/hooks/useRightDrawer', () => ({
useRightDrawer: () => ({ useRightDrawer: () => ({
closeRightDrawer: mockCloseRightDrawer, closeRightDrawer: mockCloseRightDrawer,
@ -50,10 +45,12 @@ describe('useDeleteStep', () => {
}; };
it('should delete step in draft version', async () => { it('should delete step in draft version', async () => {
const { result } = renderHook(() => const { result } = renderHook(
useDeleteStep({ () =>
workflow: mockWorkflow as unknown as WorkflowWithCurrentVersion, useDeleteStep({
}), workflow: mockWorkflow as unknown as WorkflowWithCurrentVersion,
}),
{ wrapper: RecoilRoot },
); );
await result.current.deleteStep('1'); await result.current.deleteStep('1');

View File

@ -1,3 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
@ -22,9 +23,11 @@ export const useDeleteStep = ({
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion(); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
const { closeRightDrawer } = useRightDrawer(); const { closeRightDrawer } = useRightDrawer();
const { closeCommandMenu } = useCommandMenu();
const deleteStep = async (stepId: string) => { const deleteStep = async (stepId: string) => {
closeRightDrawer(); closeRightDrawer();
closeCommandMenu();
const workflowVersion = await getUpdatableWorkflowVersion(workflow); const workflowVersion = await getUpdatableWorkflowVersion(workflow);
if (stepId === TRIGGER_STEP_ID) { if (stepId === TRIGGER_STEP_ID) {
await updateOneWorkflowVersion({ await updateOneWorkflowVersion({