583 refactor useCommandMenu hook (#10984)

Closes https://github.com/twentyhq/core-team-issues/issues/583

- Split hook into smaller hooks
- Create tests
This commit is contained in:
Raphaël Bosi
2025-03-18 15:37:28 +01:00
committed by GitHub
parent 324794707a
commit 2680f1d6be
48 changed files with 2120 additions and 918 deletions

View File

@ -38,11 +38,11 @@ const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>`
export const RecordIndexActionMenuBarAllActionsButton = () => {
const theme = useTheme();
const { openRootCommandMenu } = useCommandMenu();
const { openCommandMenu } = useCommandMenu();
return (
<>
<StyledSeparator size="md" />
<StyledButton onClick={openRootCommandMenu}>
<StyledButton onClick={openCommandMenu}>
<IconLayoutSidebarRightExpand size={theme.icon.size.md} />
<StyledButtonLabel>All Actions</StyledButtonLabel>
<StyledSeparator size="sm" />

View File

@ -10,7 +10,7 @@ import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendar
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenCalendarEventInCommandMenu } from '@/command-menu/hooks/useOpenCalendarEventInCommandMenu';
import { isDefined } from 'twenty-shared';
import {
Avatar,
@ -114,7 +114,8 @@ export const CalendarEventRow = ({
const theme = useTheme();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { displayCurrentEventCursor = false } = useContext(CalendarContext);
const { openCalendarEventInCommandMenu } = useCommandMenu();
const { openCalendarEventInCommandMenu } =
useOpenCalendarEventInCommandMenu();
const startsAt = getCalendarEventStartDate(calendarEvent);
const endsAt = getCalendarEventEndDate(calendarEvent);

View File

@ -3,7 +3,7 @@ import { Avatar, GRAY_SCALE } from 'twenty-ui';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEmailThreadInCommandMenu';
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils/date-utils';
@ -68,7 +68,7 @@ type EmailThreadPreviewProps = {
};
export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
const { openEmailThreadInCommandMenu } = useCommandMenu();
const { openEmailThreadInCommandMenu } = useOpenEmailThreadInCommandMenu();
const visibility = thread.visibility;

View File

@ -1,7 +1,6 @@
import { useSetRecoilState } from 'recoil';
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
@ -13,6 +12,7 @@ import { Note } from '@/activities/types/Note';
import { NoteTarget } from '@/activities/types/NoteTarget';
import { Task } from '@/activities/types/Task';
import { TaskTarget } from '@/activities/types/TaskTarget';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
@ -53,7 +53,7 @@ export const useOpenCreateActivityDrawer = ({
isUpsertingActivityInDBState,
);
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const openCreateActivityDrawer = async ({
targetableObjects,

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
import { Note } from '@/activities/types/Note';
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
@ -68,7 +68,7 @@ export const NoteCard = ({
note: Note;
isSingleNote: boolean;
}) => {
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const body = getActivityPreview(note?.bodyV2?.blocknote ?? null);

View File

@ -13,7 +13,7 @@ import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { Task } from '@/activities/types/Task';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { useCompleteTask } from '../hooks/useCompleteTask';
@ -78,7 +78,7 @@ const StyledCheckboxContainer = styled.div`
export const TaskRow = ({ task }: { task: Task }) => {
const theme = useTheme();
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const body = getActivitySummary(task?.bodyV2?.blocknote ?? null);

View File

@ -5,7 +5,7 @@ import {
StyledEventRowItemAction,
StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isNonEmptyString } from '@sniptt/guards';
@ -55,7 +55,7 @@ export const EventRowActivity = ({
? event.linkedRecordCachedName
: 'Untitled';
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
return (
<>

View File

@ -9,6 +9,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
@ -54,11 +55,10 @@ export const CommandMenuContainer = ({
}: {
children: React.ReactNode;
}) => {
const {
toggleCommandMenu,
closeCommandMenu,
onCommandMenuCloseAnimationComplete,
} = useCommandMenu();
const { toggleCommandMenu, closeCommandMenu } = useCommandMenu();
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
@ -154,7 +154,7 @@ export const CommandMenuContainer = ({
<ActionMenuConfirmationModals />
<AnimatePresence
mode="wait"
onExitComplete={onCommandMenuCloseAnimationComplete}
onExitComplete={commandMenuCloseAnimationCompleteCleanup}
>
{isCommandMenuOpened && (
<StyledCommandMenu

View File

@ -23,7 +23,7 @@ export const CommandMenuContextChipGroupsWithRecordSelection = ({
limit: 3,
});
const { openRootCommandMenu } = useCommandMenu();
const { openCommandMenu } = useCommandMenu();
if (loading) {
return null;
@ -46,7 +46,7 @@ export const CommandMenuContextChipGroupsWithRecordSelection = ({
totalCount,
),
Icons: Avatars,
onClick: contextChips.length > 0 ? openRootCommandMenu : undefined,
onClick: contextChips.length > 0 ? openCommandMenu : undefined,
withIconBackground: false,
}
: undefined;

View File

@ -7,6 +7,7 @@ import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/Command
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuContextChips } from '@/command-menu/hooks/useCommandMenuContextChips';
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
@ -91,7 +92,9 @@ export const CommandMenuTopBar = () => {
const isMobile = useIsMobile();
const { closeCommandMenu, goBackFromCommandMenu } = useCommandMenu();
const { closeCommandMenu } = useCommandMenu();
const { goBackFromCommandMenu } = useCommandMenuHistory();
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,

View File

@ -8,8 +8,6 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { IconList, IconSearch } from 'twenty-ui';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
@ -53,7 +51,7 @@ describe('useCommandMenu', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenu.openRootCommandMenu();
result.current.commandMenu.openCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
@ -82,164 +80,4 @@ describe('useCommandMenu', () => {
expect(result.current.isCommandMenuOpened).toBe(false);
});
it('should navigate to a page', () => {
const { result } = renderHooks();
expect(result.current.commandMenuNavigationStack).toEqual([]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
});
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
{
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
},
]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Company',
Icon: IconList,
instanceId: '2',
});
});
it('should go back from a page', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
});
});
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
{
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
},
]);
act(() => {
result.current.commandMenu.goBackFromCommandMenu();
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
});
act(() => {
result.current.commandMenu.goBackFromCommandMenu();
result.current.commandMenu.onCommandMenuCloseAnimationComplete();
});
expect(result.current.commandMenuNavigationStack).toEqual([]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
instanceId: '',
Icon: undefined,
});
expect(result.current.isCommandMenuOpened).toBe(false);
});
it('should navigate to a page in history', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
});
});
act(() => {
result.current.commandMenu.navigateCommandMenuHistory(0);
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
});
});
});

View File

@ -0,0 +1,249 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { IconList } from 'twenty-ui';
const mockCloseDropdown = jest.fn();
const mockResetContextStoreStates = jest.fn();
const mockResetSelectedItem = jest.fn();
const mockGoBackToPreviousHotkeyScope = jest.fn();
const mockEmitRightDrawerCloseEvent = jest.fn();
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
useDropdownV2: () => ({
closeDropdown: mockCloseDropdown,
}),
}));
jest.mock('@/command-menu/hooks/useResetContextStoreStates', () => ({
useResetContextStoreStates: () => ({
resetContextStoreStates: mockResetContextStoreStates,
}),
}));
jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({
useSelectableList: () => ({
resetSelectedItem: mockResetSelectedItem,
}),
}));
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
usePreviousHotkeyScope: () => ({
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
}),
}));
jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({
emitRightDrawerCloseEvent: () => {
mockEmitRightDrawerCloseEvent();
},
}));
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</RecoilRoot>
);
describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuNavigationRecords = useRecoilValue(
commandMenuNavigationRecordsState,
);
const commandMenuNavigationMorphItemByPage = useRecoilValue(
commandMenuNavigationMorphItemByPageState,
);
const hasUserSelectedCommand = useRecoilValue(
hasUserSelectedCommandState,
);
const isCommandMenuClosing = useRecoilValue(isCommandMenuClosingState);
const viewableRecordId = useRecoilValue(viewableRecordIdState);
// Get setters for state modification in tests
const setCommandMenuPage = useSetRecoilState(commandMenuPageState);
const setCommandMenuPageInfo = useSetRecoilState(
commandMenuPageInfoState,
);
const setIsCommandMenuOpened = useSetRecoilState(
isCommandMenuOpenedState,
);
const setCommandMenuSearch = useSetRecoilState(commandMenuSearchState);
const setCommandMenuNavigationStack = useSetRecoilState(
commandMenuNavigationStackState,
);
const setCommandMenuNavigationRecords = useSetRecoilState(
commandMenuNavigationRecordsState,
);
const setHasUserSelectedCommand = useSetRecoilState(
hasUserSelectedCommandState,
);
const setIsCommandMenuClosing = useSetRecoilState(
isCommandMenuClosingState,
);
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
return {
commandMenuCloseAnimationCompleteCleanup,
commandMenuPage,
commandMenuPageInfo,
isCommandMenuOpened,
commandMenuSearch,
commandMenuNavigationStack,
commandMenuNavigationRecords,
commandMenuNavigationMorphItemByPage,
hasUserSelectedCommand,
isCommandMenuClosing,
viewableRecordId,
setCommandMenuPage,
setCommandMenuPageInfo,
setIsCommandMenuOpened,
setCommandMenuSearch,
setCommandMenuNavigationStack,
setCommandMenuNavigationRecords,
setHasUserSelectedCommand,
setIsCommandMenuClosing,
setViewableRecordId,
};
},
{
wrapper: Wrapper,
},
);
return { result };
};
it('should reset modified states back to default values', () => {
const { result } = renderHooks();
act(() => {
result.current.setCommandMenuPage(CommandMenuPages.ViewRecord);
result.current.setCommandMenuPageInfo({
title: 'Test Record',
Icon: IconList,
instanceId: 'test-id',
});
result.current.setIsCommandMenuOpened(true);
result.current.setCommandMenuSearch('test search');
result.current.setCommandMenuNavigationStack([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconList,
pageId: '1',
},
]);
result.current.setCommandMenuNavigationRecords([
{
objectMetadataItem: { id: '1', nameSingular: 'Record' } as any,
record: { id: '1' } as any,
},
]);
result.current.setHasUserSelectedCommand(true);
result.current.setIsCommandMenuClosing(true);
result.current.setViewableRecordId('record-123');
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Test Record',
Icon: IconList,
instanceId: 'test-id',
});
expect(result.current.isCommandMenuOpened).toBe(true);
expect(result.current.commandMenuSearch).toBe('test search');
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconList,
pageId: '1',
},
]);
expect(result.current.commandMenuNavigationRecords).toEqual([
{
objectMetadataItem: { id: '1', nameSingular: 'Record' } as any,
record: { id: '1' } as any,
},
]);
expect(result.current.hasUserSelectedCommand).toBe(true);
expect(result.current.isCommandMenuClosing).toBe(true);
expect(result.current.viewableRecordId).toBe('record-123');
act(() => {
result.current.commandMenuCloseAnimationCompleteCleanup();
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(result.current.commandMenuSearch).toBe('');
expect(result.current.commandMenuNavigationStack).toEqual([]);
expect(result.current.commandMenuNavigationRecords).toEqual([]);
expect(result.current.hasUserSelectedCommand).toBe(false);
expect(result.current.isCommandMenuClosing).toBe(false);
expect(result.current.viewableRecordId).toBe(null);
});
it('should call all dependent functions correctly', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenuCloseAnimationCompleteCleanup();
});
expect(mockCloseDropdown).toHaveBeenCalledTimes(1);
expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2);
expect(mockResetSelectedItem).toHaveBeenCalledTimes(1);
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalledTimes(1);
expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1);
expect(mockCloseDropdown).toHaveBeenCalledWith(
COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID,
);
expect(mockResetContextStoreStates).toHaveBeenNthCalledWith(
1,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
expect(mockResetContextStoreStates).toHaveBeenNthCalledWith(
2,
COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
);
});
});

View File

@ -0,0 +1,147 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { IconList, IconSearch } from 'twenty-ui';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</RecoilRoot>
);
const renderHooks = () => {
const { result } = renderHook(
() => {
const commandMenu = useCommandMenu();
const commandMenuHistory = useCommandMenuHistory();
const commandMenuCloseAnimationCompleteCleanup =
useCommandMenuCloseAnimationCompleteCleanup();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
return {
commandMenu,
commandMenuHistory,
isCommandMenuOpened,
commandMenuNavigationStack,
commandMenuPage,
commandMenuPageInfo,
commandMenuCloseAnimationCompleteCleanup,
};
},
{
wrapper: Wrapper,
},
);
return { result };
};
describe('useCommandMenuHistory', () => {
it('should go back from a page', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
});
});
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
{
page: CommandMenuPages.ViewRecord,
pageTitle: 'Company',
pageIcon: IconList,
pageId: '2',
},
]);
act(() => {
result.current.commandMenuHistory.goBackFromCommandMenu();
});
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
},
]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
});
act(() => {
result.current.commandMenuHistory.goBackFromCommandMenu();
result.current.commandMenuCloseAnimationCompleteCleanup.commandMenuCloseAnimationCompleteCleanup();
});
expect(result.current.commandMenuNavigationStack).toEqual([]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
instanceId: '',
Icon: undefined,
});
expect(result.current.isCommandMenuOpened).toBe(false);
});
it('should navigate to a page in history', () => {
const { result } = renderHooks();
act(() => {
result.current.commandMenu.navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: '1',
});
});
act(() => {
result.current.commandMenuHistory.navigateCommandMenuHistory(0);
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
});
});
});

View File

@ -0,0 +1,101 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { Icon123, useIcons } from 'twenty-ui';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'),
}));
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
personMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreNumberOfSelectedRecords: 0,
contextStoreCurrentViewType: ContextStoreViewType.Table,
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const { getIcon } = useIcons();
return {
navigateCommandMenu,
commandMenuPage,
commandMenuNavigationStack,
commandMenuPageInfo,
getIcon,
};
},
{
wrapper,
},
);
return { result };
};
describe('useNavigateCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should navigate to the correct page', () => {
const { result } = renderHooks();
act(() => {
result.current.navigateCommandMenu({
page: CommandMenuPages.Root,
pageTitle: 'Root',
pageIcon: Icon123,
pageIconColor: 'red',
pageId: 'mocked-uuid',
resetNavigationStack: false,
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.Root,
pageTitle: 'Root',
pageIcon: Icon123,
pageIconColor: 'red',
pageId: 'mocked-uuid',
},
]);
expect(result.current.commandMenuPageInfo).toEqual({
title: 'Root',
Icon: Icon123,
instanceId: 'mocked-uuid',
});
});
});

View File

@ -0,0 +1,89 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useOpenCalendarEventInCommandMenu } from '@/command-menu/hooks/useOpenCalendarEventInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconCalendarEvent } from 'twenty-ui';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'),
}));
const mockNavigateCommandMenu = jest.fn();
jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
useNavigateCommandMenu: () => ({
navigateCommandMenu: mockNavigateCommandMenu,
}),
}));
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
personMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreNumberOfSelectedRecords: 0,
contextStoreCurrentViewType: ContextStoreViewType.Table,
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { openCalendarEventInCommandMenu } =
useOpenCalendarEventInCommandMenu();
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
return {
openCalendarEventInCommandMenu,
viewableRecordId,
};
},
{
wrapper,
},
);
return { result };
};
describe('useOpenCalendarEventInCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should set the correct states and navigate to the calendar event page', () => {
const { result } = renderHooks();
const calendarEventId = 'calendar-event-123';
act(() => {
result.current.openCalendarEventInCommandMenu(calendarEventId);
});
expect(result.current.viewableRecordId).toBe(calendarEventId);
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.ViewCalendarEvent,
pageTitle: 'Calendar Event',
pageIcon: IconCalendarEvent,
pageId: 'mocked-uuid',
});
});
});

View File

@ -0,0 +1,89 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEmailThreadInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconMail } from 'twenty-ui';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'),
}));
const mockNavigateCommandMenu = jest.fn();
jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
useNavigateCommandMenu: () => ({
navigateCommandMenu: mockNavigateCommandMenu,
}),
}));
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
personMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreNumberOfSelectedRecords: 0,
contextStoreCurrentViewType: ContextStoreViewType.Table,
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { openEmailThreadInCommandMenu } =
useOpenEmailThreadInCommandMenu();
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
return {
openEmailThreadInCommandMenu,
viewableRecordId,
};
},
{
wrapper,
},
);
return { result };
};
describe('useOpenEmailThreadInCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should set the correct states and navigate to the email thread page', () => {
const { result } = renderHooks();
const emailThreadId = 'email-thread-123';
act(() => {
result.current.openEmailThreadInCommandMenu(emailThreadId);
});
expect(result.current.viewableRecordId).toBe(emailThreadId);
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.ViewEmailThread,
pageTitle: 'Email Thread',
pageIcon: IconMail,
pageId: 'mocked-uuid',
});
});
});

View File

@ -0,0 +1,178 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIcons } from 'twenty-ui';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'),
}));
const mockNavigateCommandMenu = jest.fn();
jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
useNavigateCommandMenu: () => ({
navigateCommandMenu: mockNavigateCommandMenu,
}),
}));
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
personMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreNumberOfSelectedRecords: 0,
contextStoreCurrentViewType: ContextStoreViewType.Table,
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationMorphItemByPage = useRecoilValue(
commandMenuNavigationMorphItemByPageState,
);
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
const viewableRecordNameSingular = useRecoilComponentValueV2(
viewableRecordNameSingularComponentState,
'mocked-uuid',
);
const currentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'mocked-uuid',
);
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
'mocked-uuid',
);
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
'mocked-uuid',
);
const currentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
'mocked-uuid',
);
const { getIcon } = useIcons();
return {
openRecordInCommandMenu,
viewableRecordId,
commandMenuPage,
commandMenuNavigationMorphItemByPage,
viewableRecordNameSingular,
currentObjectMetadataItemId,
targetedRecordsRule,
numberOfSelectedRecords,
currentViewType,
getIcon,
};
},
{
wrapper,
},
);
return { result };
};
describe('useOpenRecordInCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should set the correct states and navigate to the record page', () => {
const { result } = renderHooks();
const recordId = 'record-123';
const objectNameSingular = 'person';
act(() => {
result.current.openRecordInCommandMenu({
recordId,
objectNameSingular,
});
});
expect(result.current.viewableRecordId).toBe(recordId);
expect(result.current.viewableRecordNameSingular).toBe(objectNameSingular);
expect(result.current.currentObjectMetadataItemId).toBe(
personMockObjectMetadataItem.id,
);
expect(result.current.targetedRecordsRule).toEqual({
mode: 'selection',
selectedRecordIds: [recordId],
});
expect(result.current.numberOfSelectedRecords).toBe(1);
expect(result.current.currentViewType).toBe(ContextStoreViewType.ShowPage);
expect(result.current.commandMenuNavigationMorphItemByPage.size).toBe(1);
expect(
result.current.commandMenuNavigationMorphItemByPage.get('mocked-uuid'),
).toEqual({
objectMetadataId: personMockObjectMetadataItem.id,
recordId,
});
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.ViewRecord,
pageTitle: 'Person',
pageIcon: result.current.getIcon(personMockObjectMetadataItem.icon),
pageIconColor: 'currentColor',
pageId: 'mocked-uuid',
resetNavigationStack: false,
});
});
it('should set the correct page title for a new record', () => {
const { result } = renderHooks();
const recordId = 'new-record-123';
const objectNameSingular = 'person';
act(() => {
result.current.openRecordInCommandMenu({
recordId,
objectNameSingular,
isNewRecord: true,
});
});
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.ViewRecord,
pageTitle: 'New Person',
pageIcon: result.current.getIcon(personMockObjectMetadataItem.icon),
pageIconColor: 'currentColor',
pageId: 'mocked-uuid',
resetNavigationStack: false,
});
});
});

View File

@ -0,0 +1,157 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
const mockCopyContextStoreStates = jest.fn();
jest.mock(
'@/command-menu/hooks/useCopyContextStoreAndActionMenuStates',
() => ({
useCopyContextStoreStates: () => ({
copyContextStoreStates: mockCopyContextStoreStates,
}),
}),
);
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const peopleMock = getPeopleRecordConnectionMock();
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
personMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [peopleMock[0].id, peopleMock[1].id],
},
contextStoreNumberOfSelectedRecords: 2,
contextStoreCurrentViewType: ContextStoreViewType.Table,
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]);
},
});
describe('useSetGlobalCommandMenuContext', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should reset all command menu context states', () => {
const { result } = renderHook(
() => {
const { setGlobalCommandMenuContext } =
useSetGlobalCommandMenuContext();
const targetedRecordsRule = useRecoilValue(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
const numberOfSelectedRecords = useRecoilValue(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
const filters = useRecoilValue(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
const currentViewType = useRecoilValue(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const hasUserSelectedCommand = useRecoilValue(
hasUserSelectedCommandState,
);
return {
setGlobalCommandMenuContext,
targetedRecordsRule,
numberOfSelectedRecords,
filters,
currentViewType,
commandMenuPageInfo,
hasUserSelectedCommand,
};
},
{
wrapper,
},
);
expect(result.current.targetedRecordsRule).toEqual({
mode: 'selection',
selectedRecordIds: [peopleMock[0].id, peopleMock[1].id],
});
expect(result.current.numberOfSelectedRecords).toBe(2);
expect(result.current.filters).toEqual([]);
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
expect(result.current.hasUserSelectedCommand).toBe(false);
act(() => {
result.current.setGlobalCommandMenuContext();
});
expect(result.current.targetedRecordsRule).toEqual({
mode: 'selection',
selectedRecordIds: [],
});
expect(result.current.numberOfSelectedRecords).toBe(0);
expect(result.current.filters).toEqual([]);
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
expect(result.current.commandMenuPageInfo).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
expect(result.current.hasUserSelectedCommand).toBe(false);
});
it('should call copyContextStoreStates with correct parameters', () => {
const { result } = renderHook(() => useSetGlobalCommandMenuContext(), {
wrapper,
});
act(() => {
result.current.setGlobalCommandMenuContext();
});
expect(mockCopyContextStoreStates).toHaveBeenCalledWith({
instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
});
});
});

View File

@ -0,0 +1,207 @@
import { renderHook } from '@testing-library/react';
import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import { act } from 'react';
import { IconBolt, IconSettingsAutomation, useIcons } from 'twenty-ui';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { useWorkflowCommandMenu } from '../useWorkflowCommandMenu';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('mocked-uuid'),
}));
const mockNavigateCommandMenu = jest.fn();
jest.mock('@/command-menu/hooks/useNavigateCommandMenu', () => ({
useNavigateCommandMenu: () => ({
navigateCommandMenu: mockNavigateCommandMenu,
}),
}));
const workflowMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'workflow',
)!;
jest.mock('@/object-metadata/hooks/useObjectMetadataItem', () => ({
useObjectMetadataItem: jest.fn(() => ({
objectMetadataItem: workflowMockObjectMetadataItem,
})),
}));
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
contextStoreCurrentObjectMetadataNameSingular:
workflowMockObjectMetadataItem.nameSingular,
contextStoreCurrentViewId: 'my-view-id',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreNumberOfSelectedRecords: 0,
contextStoreCurrentViewType: ContextStoreViewType.Table,
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const {
openWorkflowTriggerTypeInCommandMenu,
openStepSelectInCommandMenu,
openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu,
} = useWorkflowCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationMorphItemByPage = useRecoilValue(
commandMenuNavigationMorphItemByPageState,
);
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
const viewableRecordNameSingular = useRecoilComponentValueV2(
viewableRecordNameSingularComponentState,
'mocked-uuid',
);
const currentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'mocked-uuid',
);
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
'mocked-uuid',
);
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
'mocked-uuid',
);
const currentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
'mocked-uuid',
);
const workflowId = useRecoilComponentValueV2(
workflowIdComponentState,
'mocked-uuid',
);
const { getIcon } = useIcons();
return {
openWorkflowTriggerTypeInCommandMenu,
openStepSelectInCommandMenu,
openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu,
workflowId,
viewableRecordId,
commandMenuPage,
commandMenuNavigationMorphItemByPage,
viewableRecordNameSingular,
currentObjectMetadataItemId,
targetedRecordsRule,
numberOfSelectedRecords,
currentViewType,
getIcon,
};
},
{
wrapper,
},
);
return { result };
};
describe('useWorkflowCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should navigate to the workflow step select trigger type page', () => {
const { result } = renderHooks();
act(() => {
result.current.openWorkflowTriggerTypeInCommandMenu('test-workflow-id');
});
expect(result.current.workflowId).toBe('test-workflow-id');
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.WorkflowStepSelectTriggerType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId: 'mocked-uuid',
});
});
it('should navigate to the workflow step select action page', () => {
const { result } = renderHooks();
act(() => {
result.current.openStepSelectInCommandMenu('test-workflow-id');
});
expect(result.current.workflowId).toBe('test-workflow-id');
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.WorkflowStepSelectAction,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId: 'mocked-uuid',
});
});
it('should navigate to the workflow step edit page', () => {
const { result } = renderHooks();
act(() => {
result.current.openWorkflowEditStepInCommandMenu(
'test-workflow-id',
'Edit Step',
IconSettingsAutomation,
);
});
expect(result.current.workflowId).toBe('test-workflow-id');
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: 'Edit Step',
pageIcon: IconSettingsAutomation,
pageId: 'mocked-uuid',
});
});
it('should navigate to the workflow step view page', () => {
const { result } = renderHooks();
act(() => {
result.current.openWorkflowViewStepInCommandMenu(
'test-workflow-id',
'View Step',
IconSettingsAutomation,
);
});
expect(result.current.workflowId).toBe('test-workflow-id');
expect(mockNavigateCommandMenu).toHaveBeenCalledWith({
page: CommandMenuPages.WorkflowStepView,
pageTitle: 'View Step',
pageIcon: IconSettingsAutomation,
pageId: 'mocked-uuid',
});
});
});

View File

@ -1,79 +1,17 @@
import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import {
IconBolt,
IconCalendarEvent,
IconComponent,
IconDotsVertical,
IconMail,
IconSearch,
IconSettingsAutomation,
useIcons,
} from 'twenty-ui';
import { IconDotsVertical } from 'twenty-ui';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { capitalize, isDefined } from 'twenty-shared';
import { v4 } from 'uuid';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export type CommandMenuNavigationStackItem = {
page: CommandMenuPages;
pageTitle: string;
pageIcon: IconComponent;
pageIconColor?: string;
pageId?: string;
};
export const useCommandMenu = () => {
const { resetSelectedItem } = useSelectableList('command-menu-list');
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const { getIcon } = useIcons();
const { copyContextStoreStates } = useCopyContextStoreStates();
const { resetContextStoreStates } = useResetContextStoreStates();
const { closeDropdown } = useDropdownV2();
const theme = useTheme();
const { navigateCommandMenu } = useNavigateCommandMenu();
const closeCommandMenu = useRecoilCallback(
({ set }) =>
@ -85,141 +23,7 @@ export const useCommandMenu = () => {
[],
);
const onCommandMenuCloseAnimationComplete = useRecoilCallback(
({ set }) =>
() => {
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(isCommandMenuOpenedState, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationMorphItemByPageState, new Map());
set(commandMenuNavigationRecordsState, []);
set(commandMenuNavigationStackState, []);
resetSelectedItem();
set(hasUserSelectedCommandState, false);
goBackToPreviousHotkeyScope();
emitRightDrawerCloseEvent();
set(isCommandMenuClosingState, false);
},
[
closeDropdown,
goBackToPreviousHotkeyScope,
resetContextStoreStates,
resetSelectedItem,
],
);
const openCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
if (isCommandMenuClosing) {
onCommandMenuCloseAnimationComplete();
}
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
if (isCommandMenuOpened) {
return;
}
copyContextStoreStates({
instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
set(isCommandMenuOpenedState, true);
set(hasUserSelectedCommandState, false);
set(isDragSelectionStartEnabledState, false);
},
[
copyContextStoreStates,
onCommandMenuCloseAnimationComplete,
setHotkeyScopeAndMemorizePreviousScope,
],
);
const navigateCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return ({
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
}) => {
if (!pageId) {
pageId = v4();
}
openCommandMenu();
set(commandMenuPageState, page);
set(commandMenuPageInfoState, {
title: pageTitle,
Icon: pageIcon,
instanceId: pageId,
});
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
const currentNavigationStack = isCommandMenuClosing
? []
: snapshot.getLoadable(commandMenuNavigationStackState).getValue();
if (resetNavigationStack) {
set(commandMenuNavigationStackState, [
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
},
]);
set(commandMenuNavigationRecordsState, []);
set(commandMenuNavigationMorphItemByPageState, new Map());
} else {
set(commandMenuNavigationStackState, [
...currentNavigationStack,
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
},
]);
}
};
},
[openCommandMenu],
);
const openRootCommandMenu = useCallback(() => {
const openCommandMenu = useCallback(() => {
navigateCommandMenu({
page: CommandMenuPages.Root,
pageTitle: 'Command Menu',
@ -240,503 +44,16 @@ export const useCommandMenu = () => {
if (isCommandMenuOpened) {
closeCommandMenu();
} else {
openRootCommandMenu();
openCommandMenu();
}
},
[closeCommandMenu, openRootCommandMenu],
);
const goBackFromCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return () => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(0, -1);
const lastNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(lastNavigationStackItem)) {
closeCommandMenu();
return;
}
set(commandMenuPageState, lastNavigationStackItem.page);
set(commandMenuPageInfoState, {
title: lastNavigationStackItem.pageTitle,
Icon: lastNavigationStackItem.pageIcon,
instanceId: lastNavigationStackItem.pageId,
});
set(commandMenuNavigationStackState, newNavigationStack);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
if (currentNavigationStack.length > 0) {
const removedItem = currentNavigationStack.at(-1);
if (isDefined(removedItem)) {
const newMorphItems = new Map(currentMorphItems);
newMorphItems.delete(removedItem.pageId);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
}
}
set(hasUserSelectedCommandState, false);
};
},
[closeCommandMenu],
);
const navigateCommandMenuHistory = useRecoilCallback(({ snapshot, set }) => {
return (pageIndex: number) => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
set(commandMenuNavigationStackState, newNavigationStack);
const newNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(newNavigationStackItem)) {
throw new Error(
`No command menu navigation stack item found for index ${pageIndex}`,
);
}
set(commandMenuPageState, newNavigationStackItem?.page);
set(commandMenuPageInfoState, {
title: newNavigationStackItem?.pageTitle,
Icon: newNavigationStackItem?.pageIcon,
instanceId: newNavigationStackItem?.pageId,
});
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
const newMorphItems = new Map(
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
newNavigationStack.some((item) => item.pageId === pageId),
),
);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
set(hasUserSelectedCommandState, false);
};
}, []);
const openRecordInCommandMenu = useRecoilCallback(
({ set, snapshot }) => {
return ({
recordId,
objectNameSingular,
isNewRecord = false,
}: {
recordId: string;
objectNameSingular: string;
isNewRecord?: boolean;
}) => {
const pageComponentInstanceId = v4();
set(
viewableRecordNameSingularComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectNameSingular,
);
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
recordId,
);
set(viewableRecordIdState, recordId);
const objectMetadataItem = snapshot
.getLoadable(
objectMetadataItemFamilySelector({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
)
.getValue();
if (!objectMetadataItem) {
throw new Error(
`No object metadata item found for object name ${objectNameSingular}`,
);
}
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectMetadataItem.id,
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
{
mode: 'selection',
selectedRecordIds: [recordId],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
1,
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
ContextStoreViewType.ShowPage,
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
)
.getValue(),
);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
const morphItemToAdd = {
objectMetadataId: objectMetadataItem.id,
recordId,
};
const newMorphItems = new Map([
...currentMorphItems,
[pageComponentInstanceId, morphItemToAdd],
]);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
const Icon = objectMetadataItem?.icon
? getIcon(objectMetadataItem.icon)
: getIcon('IconList');
const IconColor = getIconColorForObjectType({
objectType: objectMetadataItem.nameSingular,
theme,
});
const capitalizedObjectNameSingular = capitalize(objectNameSingular);
navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: isNewRecord
? t`New ${capitalizedObjectNameSingular}`
: capitalizedObjectNameSingular,
pageIcon: Icon,
pageIconColor: IconColor,
pageId: pageComponentInstanceId,
resetNavigationStack: false,
});
};
},
[getIcon, navigateCommandMenu, theme],
);
const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectTriggerType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowActionInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectAction,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowEditStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewRunStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openRecordsSearchPage = () => {
navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
pageIcon: IconSearch,
pageId: v4(),
});
};
const openCalendarEventInCommandMenu = useRecoilCallback(
({ set }) => {
return (calendarEventId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
calendarEventId,
);
// TODO: Uncomment this once we need to calendar event title in the navigation
// const objectMetadataItem = snapshot
// .getLoadable(objectMetadataItemsState)
// .getValue()
// .find(
// ({ nameSingular }) =>
// nameSingular === CoreObjectNameSingular.CalendarEvent,
// );
// set(
// commandMenuNavigationMorphItemsState,
// new Map([
// ...snapshot
// .getLoadable(commandMenuNavigationMorphItemsState)
// .getValue(),
// [
// pageComponentInstanceId,
// {
// objectMetadataId: objectMetadataItem?.id,
// recordId: calendarEventId,
// },
// ],
// ]),
// );
navigateCommandMenu({
page: CommandMenuPages.ViewCalendarEvent,
pageTitle: 'Calendar Event',
pageIcon: IconCalendarEvent,
pageId: pageComponentInstanceId,
});
};
},
[navigateCommandMenu],
);
const openEmailThreadInCommandMenu = useRecoilCallback(
({ set }) => {
return (emailThreadId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
emailThreadId,
);
// TODO: Uncomment this once we need to show the thread title in the navigation
// const objectMetadataItem = snapshot
// .getLoadable(objectMetadataItemsState)
// .getValue()
// .find(
// ({ nameSingular }) =>
// nameSingular === CoreObjectNameSingular.MessageThread,
// );
// set(
// commandMenuNavigationMorphItemsState,
// new Map([
// ...snapshot
// .getLoadable(commandMenuNavigationMorphItemsState)
// .getValue(),
// [
// pageComponentInstanceId,
// {
// objectMetadataId: objectMetadataItem?.id,
// recordId: emailThreadId,
// },
// ],
// ]),
// );
navigateCommandMenu({
page: CommandMenuPages.ViewEmailThread,
pageTitle: 'Email Thread',
pageIcon: IconMail,
pageId: pageComponentInstanceId,
});
};
},
[navigateCommandMenu],
);
const setGlobalCommandMenuContext = useRecoilCallback(
({ set }) => {
return () => {
copyContextStoreStates({
instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
});
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(hasUserSelectedCommandState, false);
};
},
[copyContextStoreStates],
[closeCommandMenu, openCommandMenu],
);
return {
openRootCommandMenu,
openCommandMenu,
closeCommandMenu,
onCommandMenuCloseAnimationComplete,
navigateCommandMenu,
navigateCommandMenuHistory,
goBackFromCommandMenu,
openRecordsSearchPage,
openRecordInCommandMenu,
toggleCommandMenu,
setGlobalCommandMenuContext,
openCalendarEventInCommandMenu,
openEmailThreadInCommandMenu,
openWorkflowTriggerTypeInCommandMenu,
openWorkflowActionInCommandMenu,
openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu,
openWorkflowViewRunStepInCommandMenu,
};
};

View File

@ -0,0 +1,69 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilCallback } from 'recoil';
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
const { resetSelectedItem } = useSelectableList('command-menu-list');
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const { resetContextStoreStates } = useResetContextStoreStates();
const { closeDropdown } = useDropdownV2();
const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
({ set }) =>
() => {
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(isCommandMenuOpenedState, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationMorphItemByPageState, new Map());
set(commandMenuNavigationRecordsState, []);
set(commandMenuNavigationStackState, []);
resetSelectedItem();
set(hasUserSelectedCommandState, false);
goBackToPreviousHotkeyScope();
emitRightDrawerCloseEvent();
set(isCommandMenuClosingState, false);
},
[
closeDropdown,
goBackToPreviousHotkeyScope,
resetContextStoreStates,
resetSelectedItem,
],
);
return {
commandMenuCloseAnimationCompleteCleanup,
};
};

View File

@ -1,5 +1,5 @@
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
@ -24,7 +24,7 @@ export const useCommandMenuContextChips = () => {
commandMenuNavigationStackState,
);
const { navigateCommandMenuHistory } = useCommandMenu();
const { navigateCommandMenuHistory } = useCommandMenuHistory();
const theme = useTheme();

View File

@ -0,0 +1,103 @@
import { useRecoilCallback } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isDefined } from 'twenty-shared';
export const useCommandMenuHistory = () => {
const { closeCommandMenu } = useCommandMenu();
const goBackFromCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return () => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(0, -1);
const lastNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(lastNavigationStackItem)) {
closeCommandMenu();
return;
}
set(commandMenuPageState, lastNavigationStackItem.page);
set(commandMenuPageInfoState, {
title: lastNavigationStackItem.pageTitle,
Icon: lastNavigationStackItem.pageIcon,
instanceId: lastNavigationStackItem.pageId,
});
set(commandMenuNavigationStackState, newNavigationStack);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
if (currentNavigationStack.length > 0) {
const removedItem = currentNavigationStack.at(-1);
if (isDefined(removedItem)) {
const newMorphItems = new Map(currentMorphItems);
newMorphItems.delete(removedItem.pageId);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
}
}
set(hasUserSelectedCommandState, false);
};
},
[closeCommandMenu],
);
const navigateCommandMenuHistory = useRecoilCallback(({ snapshot, set }) => {
return (pageIndex: number) => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
set(commandMenuNavigationStackState, newNavigationStack);
const newNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(newNavigationStackItem)) {
throw new Error(
`No command menu navigation stack item found for index ${pageIndex}`,
);
}
set(commandMenuPageState, newNavigationStackItem.page);
set(commandMenuPageInfoState, {
title: newNavigationStackItem.pageTitle,
Icon: newNavigationStackItem.pageIcon,
instanceId: newNavigationStackItem.pageId,
});
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
const newMorphItems = new Map(
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
newNavigationStack.some((item) => item.pageId === pageId),
),
);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
set(hasUserSelectedCommandState, false);
};
}, []);
return {
goBackFromCommandMenu,
navigateCommandMenuHistory,
};
};

View File

@ -1,5 +1,8 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
@ -13,12 +16,13 @@ import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export const useCommandMenuHotKeys = () => {
const {
openRecordsSearchPage,
toggleCommandMenu,
goBackFromCommandMenu,
setGlobalCommandMenuContext,
} = useCommandMenu();
const { toggleCommandMenu } = useCommandMenu();
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
const { goBackFromCommandMenu } = useCommandMenuHistory();
const { setGlobalCommandMenuContext } = useSetGlobalCommandMenuContext();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);

View File

@ -0,0 +1,137 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { commandMenuNavigationRecordsState } from '@/command-menu/states/commandMenuNavigationRecordsState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useRecoilCallback } from 'recoil';
import { IconComponent } from 'twenty-ui';
import { v4 } from 'uuid';
export type CommandMenuNavigationStackItem = {
page: CommandMenuPages;
pageTitle: string;
pageIcon: IconComponent;
pageIconColor?: string;
pageId?: string;
};
export const useNavigateCommandMenu = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { copyContextStoreStates } = useCopyContextStoreStates();
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const openCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
if (isCommandMenuClosing) {
commandMenuCloseAnimationCompleteCleanup();
}
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
if (isCommandMenuOpened) {
return;
}
copyContextStoreStates({
instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
set(isCommandMenuOpenedState, true);
set(hasUserSelectedCommandState, false);
set(isDragSelectionStartEnabledState, false);
},
[
copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup,
setHotkeyScopeAndMemorizePreviousScope,
],
);
const navigateCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return ({
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
}) => {
const computedPageId = pageId || v4();
openCommandMenu();
set(commandMenuPageState, page);
set(commandMenuPageInfoState, {
title: pageTitle,
Icon: pageIcon,
instanceId: computedPageId,
});
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
const currentNavigationStack = isCommandMenuClosing
? []
: snapshot.getLoadable(commandMenuNavigationStackState).getValue();
if (resetNavigationStack) {
set(commandMenuNavigationStackState, [
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
set(commandMenuNavigationRecordsState, []);
set(commandMenuNavigationMorphItemByPageState, new Map());
} else {
set(commandMenuNavigationStackState, [
...currentNavigationStack,
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
}
};
},
[openCommandMenu],
);
return {
navigateCommandMenu,
};
};

View File

@ -0,0 +1,63 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { IconCalendarEvent } from 'twenty-ui';
import { v4 } from 'uuid';
export const useOpenCalendarEventInCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openCalendarEventInCommandMenu = useRecoilCallback(
({ set }) => {
return (calendarEventId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
calendarEventId,
);
// TODO: Uncomment this once we need to calendar event title in the navigation
// const objectMetadataItem = snapshot
// .getLoadable(objectMetadataItemsState)
// .getValue()
// .find(
// ({ nameSingular }) =>
// nameSingular === CoreObjectNameSingular.CalendarEvent,
// );
// set(
// commandMenuNavigationMorphItemsState,
// new Map([
// ...snapshot
// .getLoadable(commandMenuNavigationMorphItemsState)
// .getValue(),
// [
// pageComponentInstanceId,
// {
// objectMetadataId: objectMetadataItem?.id,
// recordId: calendarEventId,
// },
// ],
// ]),
// );
navigateCommandMenu({
page: CommandMenuPages.ViewCalendarEvent,
pageTitle: t`Calendar Event`,
pageIcon: IconCalendarEvent,
pageId: pageComponentInstanceId,
});
};
},
[navigateCommandMenu],
);
return {
openCalendarEventInCommandMenu,
};
};

View File

@ -0,0 +1,62 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useRecoilCallback } from 'recoil';
import { IconMail } from 'twenty-ui';
import { v4 } from 'uuid';
export const useOpenEmailThreadInCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openEmailThreadInCommandMenu = useRecoilCallback(
({ set }) => {
return (emailThreadId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
emailThreadId,
);
// TODO: Uncomment this once we need to show the thread title in the navigation
// const objectMetadataItem = snapshot
// .getLoadable(objectMetadataItemsState)
// .getValue()
// .find(
// ({ nameSingular }) =>
// nameSingular === CoreObjectNameSingular.MessageThread,
// );
// set(
// commandMenuNavigationMorphItemsState,
// new Map([
// ...snapshot
// .getLoadable(commandMenuNavigationMorphItemsState)
// .getValue(),
// [
// pageComponentInstanceId,
// {
// objectMetadataId: objectMetadataItem?.id,
// recordId: emailThreadId,
// },
// ],
// ]),
// );
navigateCommandMenu({
page: CommandMenuPages.ViewEmailThread,
pageTitle: 'Email Thread',
pageIcon: IconMail,
pageId: pageComponentInstanceId,
});
};
},
[navigateCommandMenu],
);
return {
openEmailThreadInCommandMenu,
};
};

View File

@ -0,0 +1,158 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { capitalize } from 'twenty-shared';
import { useIcons } from 'twenty-ui';
import { v4 } from 'uuid';
export const useOpenRecordInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const theme = useTheme();
const { getIcon } = useIcons();
const openRecordInCommandMenu = useRecoilCallback(
({ set, snapshot }) => {
return ({
recordId,
objectNameSingular,
isNewRecord = false,
}: {
recordId: string;
objectNameSingular: string;
isNewRecord?: boolean;
}) => {
const pageComponentInstanceId = v4();
set(
viewableRecordNameSingularComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectNameSingular,
);
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
recordId,
);
set(viewableRecordIdState, recordId);
const objectMetadataItem = snapshot
.getLoadable(
objectMetadataItemFamilySelector({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
)
.getValue();
if (!objectMetadataItem) {
throw new Error(
`No object metadata item found for object name ${objectNameSingular}`,
);
}
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectMetadataItem.id,
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
{
mode: 'selection',
selectedRecordIds: [recordId],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
1,
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
ContextStoreViewType.ShowPage,
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
)
.getValue(),
);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemByPageState)
.getValue();
const morphItemToAdd = {
objectMetadataId: objectMetadataItem.id,
recordId,
};
const newMorphItems = new Map(currentMorphItems);
newMorphItems.set(pageComponentInstanceId, morphItemToAdd);
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
const Icon = objectMetadataItem?.icon
? getIcon(objectMetadataItem.icon)
: getIcon('IconList');
const IconColor = getIconColorForObjectType({
objectType: objectMetadataItem.nameSingular,
theme,
});
const capitalizedObjectNameSingular = capitalize(objectNameSingular);
navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: isNewRecord
? t`New ${capitalizedObjectNameSingular}`
: capitalizedObjectNameSingular,
pageIcon: Icon,
pageIconColor: IconColor,
pageId: pageComponentInstanceId,
resetNavigationStack: false,
});
};
},
[getIcon, navigateCommandMenu, theme],
);
return {
openRecordInCommandMenu,
};
};

View File

@ -0,0 +1,22 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { t } from '@lingui/core/macro';
import { IconSearch } from 'twenty-ui';
import { v4 } from 'uuid';
export const useOpenRecordsSearchPageInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const openRecordsSearchPage = () => {
navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
pageTitle: t`Search`,
pageIcon: IconSearch,
pageId: v4(),
});
};
return {
openRecordsSearchPage,
};
};

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { t } from '@lingui/core/macro';
@ -24,7 +24,7 @@ export const useSearchRecords = () => {
},
});
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const commands = useMemo(() => {
return (globalSearchData?.globalSearch ?? []).map((searchRecord) => {

View File

@ -0,0 +1,70 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilCallback } from 'recoil';
export const useSetGlobalCommandMenuContext = () => {
const { copyContextStoreStates } = useCopyContextStoreStates();
const setGlobalCommandMenuContext = useRecoilCallback(
({ set }) => {
return () => {
copyContextStoreStates({
instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
});
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(hasUserSelectedCommandState, false);
};
},
[copyContextStoreStates],
);
return {
setGlobalCommandMenuContext,
};
};

View File

@ -0,0 +1,124 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { IconBolt, IconComponent, IconSettingsAutomation } from 'twenty-ui';
import { v4 } from 'uuid';
export const useWorkflowCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectTriggerType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId,
});
};
},
[navigateCommandMenu],
);
const openStepSelectInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectAction,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowEditStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowRunViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
return {
openWorkflowTriggerTypeInCommandMenu,
openStepSelectInCommandMenu,
openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu,
openWorkflowRunViewStepInCommandMenu,
};
};

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import {
WorkflowTriggerType,
@ -26,7 +26,7 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({
type,

View File

@ -2,7 +2,7 @@ import { useLocation } from 'react-router-dom';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { IconSearch, IconSettings } from 'twenty-ui';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { CurrentWorkspaceMemberFavoritesFolders } from '@/favorites/components/CurrentWorkspaceMemberFavoritesFolders';
import { WorkspaceFavorites } from '@/favorites/components/WorkspaceFavorites';
import { NavigationDrawerOpenedSection } from '@/object-metadata/components/NavigationDrawerOpenedSection';
@ -41,7 +41,7 @@ export const MainNavigationDrawerItems = () => {
const { t } = useLingui();
const { openRecordsSearchPage } = useCommandMenu();
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
return (
<>

View File

@ -1,4 +1,5 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
@ -17,7 +18,8 @@ type NavigationBarItemName = 'main' | 'search' | 'tasks' | 'settings';
export const MobileNavigationBar = () => {
const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState);
const { closeCommandMenu, openRecordsSearchPage } = useCommandMenu();
const { closeCommandMenu } = useCommandMenu();
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
const isSettingsPage = useIsSettingsPage();
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState);

View File

@ -6,7 +6,7 @@ import {
isModifiedEvent,
} from 'twenty-ui';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@ -39,7 +39,7 @@ export const RecordChip = ({
record,
});
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);

View File

@ -1,13 +1,13 @@
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
@ -81,7 +81,7 @@ export const RecordBoardCard = ({
position?: 'first' | 'last';
}) => {
const navigate = useNavigateApp();
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { recordId } = useContext(RecordBoardCardContext);

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
@ -38,7 +38,7 @@ export const RecordBoardColumnNewOpportunity = ({
viewableRecordNameSingularState,
);
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const createCompanyOpportunityAndOpenCommandMenu = async (
searchInput?: string,

View File

@ -1,7 +1,7 @@
import { useSetRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
@ -42,7 +42,7 @@ export const useAddNewRecordAndOpenRightDrawer = ({
.nameSingular ?? 'workspaceMember',
});
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
if (
relationObjectMetadataNameSingular === 'workspaceMember' ||

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@ -39,7 +39,7 @@ export const useCreateNewTableRecord = ({
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { createOneRecord } = useCreateOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,

View File

@ -17,7 +17,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from 'twenty-shared';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
@ -75,7 +75,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const { openRecordInCommandMenu } = useCommandMenu();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const { openFieldInput } = useOpenFieldInputEditMode();

View File

@ -2,9 +2,9 @@ import { useCallback, useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
@ -27,7 +27,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const {
openWorkflowTriggerTypeInCommandMenu,
openWorkflowEditStepInCommandMenu,
} = useCommandMenu();
} = useWorkflowCommandMenu();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
@ -16,7 +16,7 @@ import { useIcons } from 'twenty-ui';
export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowViewStepInCommandMenu } = useCommandMenu();
const { openWorkflowViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
@ -20,7 +20,7 @@ import { useIcons } from 'twenty-ui';
export const WorkflowRunDiagramCanvasEffect = () => {
const { getIcon } = useIcons();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowViewRunStepInCommandMenu } = useCommandMenu();
const { openWorkflowRunViewStepInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
@ -68,7 +68,7 @@ export const WorkflowRunDiagramCanvasEffect = () => {
}
if (isDefined(workflowId)) {
openWorkflowViewRunStepInCommandMenu(
openWorkflowRunViewStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
@ -80,7 +80,7 @@ export const WorkflowRunDiagramCanvasEffect = () => {
workflowId,
getIcon,
goBackToFirstWorkflowRunRightDrawerTabIfNeeded,
openWorkflowViewRunStepInCommandMenu,
openWorkflowRunViewStepInCommandMenu,
],
);

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState';
import { isDefined } from 'twenty-shared';
@ -10,7 +10,7 @@ export const useStartNodeCreation = () => {
const setWorkflowCreateStepFromParentStepId = useSetRecoilState(
workflowCreateStepFromParentStepIdState,
);
const { openWorkflowActionInCommandMenu } = useCommandMenu();
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
@ -23,14 +23,14 @@ export const useStartNodeCreation = () => {
setWorkflowCreateStepFromParentStepId(parentNodeId);
if (isDefined(workflowId)) {
openWorkflowActionInCommandMenu(workflowId);
openStepSelectInCommandMenu(workflowId);
return;
}
},
[
setWorkflowCreateStepFromParentStepId,
workflowId,
openWorkflowActionInCommandMenu,
openStepSelectInCommandMenu,
],
);

View File

@ -1,4 +1,4 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import {
WorkflowTriggerType,
@ -26,7 +26,7 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const handleTriggerTypeClick = ({
type,

View File

@ -2,12 +2,14 @@ import { PropsWithChildren, useEffect, useState } from 'react';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import {
ContextStoreTargetedRecordsRule,
contextStoreTargetedRecordsRuleComponentState,
} from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -18,6 +20,7 @@ export type JestContextStoreSetterMocks = {
contextStoreFilters?: RecordFilter[];
contextStoreCurrentObjectMetadataNameSingular?: string;
contextStoreCurrentViewId?: string;
contextStoreCurrentViewType?: ContextStoreViewType;
};
type JestContextStoreSetterProps =
@ -31,6 +34,7 @@ export const JestContextStoreSetter = ({
contextStoreNumberOfSelectedRecords = 0,
contextStoreCurrentObjectMetadataNameSingular = 'company',
contextStoreFilters = [],
contextStoreCurrentViewType,
children,
}: JestContextStoreSetterProps) => {
const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
@ -54,6 +58,10 @@ export const JestContextStoreSetter = ({
contextStoreCurrentViewIdComponentState,
);
const setContextStoreCurrentViewType = useSetRecoilComponentStateV2(
contextStoreCurrentViewTypeComponentState,
);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: contextStoreCurrentObjectMetadataNameSingular,
});
@ -67,7 +75,7 @@ export const JestContextStoreSetter = ({
setContextStoreCurrentObjectMetadataItemId(objectMetadataItem.id);
setContextStoreNumberOfSelectedRecords(contextStoreNumberOfSelectedRecords);
setcontextStoreFiltersComponentState(contextStoreFilters);
setContextStoreCurrentViewType(contextStoreCurrentViewType ?? null);
setIsLoaded(true);
}, [
setContextStoreTargetedRecordsRule,
@ -81,6 +89,8 @@ export const JestContextStoreSetter = ({
objectMetadataItem,
setContextStoreCurrentViewId,
contextStoreCurrentViewId,
setContextStoreCurrentViewType,
contextStoreCurrentViewType,
]);
return isLoaded ? <>{children}</> : null;

View File

@ -28,6 +28,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
onInitializeRecoilSnapshot,
contextStoreTargetedRecordsRule,
contextStoreCurrentViewId,
contextStoreCurrentViewType,
contextStoreNumberOfSelectedRecords,
contextStoreCurrentObjectMetadataNameSingular,
contextStoreFilters,
@ -93,6 +94,7 @@ export const getJestMetadataAndApolloMocksAndActionMenuWrapper = ({
contextStoreCurrentObjectMetadataNameSingular={
contextStoreCurrentObjectMetadataNameSingular
}
contextStoreCurrentViewType={contextStoreCurrentViewType}
>
{children}
</JestContextStoreSetter>