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 { IconDotsVertical, IconSearch, useIcons } 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 { CommandMenuNavigationStackItem, commandMenuNavigationStackState, } from '@/command-menu/states/commandMenuNavigationStackState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageTitle'; import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState'; 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 { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState'; 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 { RIGHT_DRAWER_RECORD_INSTANCE_ID } from '@/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; 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 { t } from '@lingui/core/macro'; import { useCallback } from 'react'; import { capitalize, isDefined } from 'twenty-shared'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; 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 closeCommandMenu = useRecoilCallback( ({ set }) => () => { set(isCommandMenuOpenedState, false); set(isCommandMenuClosingState, true); set(isDragSelectionStartEnabledState, true); }, [], ); 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, }); set(isCommandMenuOpenedState, false); set(commandMenuSearchState, ''); 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, resetNavigationStack = false, }: CommandMenuNavigationStackItem & { resetNavigationStack?: boolean; }) => { openCommandMenu(); set(commandMenuPageState, page); set(commandMenuPageInfoState, { title: pageTitle, Icon: pageIcon, }); const isCommandMenuClosing = snapshot .getLoadable(isCommandMenuClosingState) .getValue(); const currentNavigationStack = isCommandMenuClosing ? [] : snapshot.getLoadable(commandMenuNavigationStackState).getValue(); const itemIsAlreadyInStack = currentNavigationStack.some( (item) => item.page === page, ); if (resetNavigationStack || itemIsAlreadyInStack) { set(commandMenuNavigationStackState, [{ page, pageTitle, pageIcon }]); } else { set(commandMenuNavigationStackState, [ ...currentNavigationStack, { page, pageTitle, pageIcon }, ]); } }; }, [openCommandMenu], ); const openRootCommandMenu = useCallback(() => { navigateCommandMenu({ page: CommandMenuPages.Root, pageTitle: 'Command Menu', pageIcon: IconDotsVertical, resetNavigationStack: true, }); }, [navigateCommandMenu]); const toggleCommandMenu = useRecoilCallback( ({ snapshot, set }) => async () => { const isCommandMenuOpened = snapshot .getLoadable(isCommandMenuOpenedState) .getValue(); set(commandMenuSearchState, ''); if (isCommandMenuOpened) { closeCommandMenu(); } else { openRootCommandMenu(); } }, [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, }); set(commandMenuNavigationStackState, newNavigationStack); 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, }); set(hasUserSelectedCommandState, false); }; }, []); const openRecordInCommandMenu = useRecoilCallback( ({ set, snapshot }) => { return ({ recordId, objectNameSingular, isNewRecord = false, }: { recordId: string; objectNameSingular: string; isNewRecord?: boolean; }) => { set(viewableRecordNameSingularState, objectNameSingular); 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( contextStoreCurrentObjectMetadataItemComponentState.atomFamily({ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID, }), objectMetadataItem, ); set( contextStoreTargetedRecordsRuleComponentState.atomFamily({ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID, }), { mode: 'selection', selectedRecordIds: [recordId], }, ); set( contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID, }), 1, ); set( contextStoreCurrentViewTypeComponentState.atomFamily({ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID, }), ContextStoreViewType.ShowPage, ); set( contextStoreCurrentViewIdComponentState.atomFamily({ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID, }), snapshot .getLoadable( contextStoreCurrentViewIdComponentState.atomFamily({ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID, }), ) .getValue(), ); const Icon = objectMetadataItem?.icon ? getIcon(objectMetadataItem.icon) : getIcon('IconList'); const capitalizedObjectNameSingular = capitalize(objectNameSingular); navigateCommandMenu({ page: CommandMenuPages.ViewRecord, pageTitle: isNewRecord ? t`New ${capitalizedObjectNameSingular}` : capitalizedObjectNameSingular, pageIcon: Icon, // TODO: remove this once we can store the navigation stack page states resetNavigationStack: true, }); }; }, [getIcon, navigateCommandMenu], ); const openRecordsSearchPage = () => { navigateCommandMenu({ page: CommandMenuPages.SearchRecords, pageTitle: 'Search', pageIcon: IconSearch, }); }; 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, }); set(hasUserSelectedCommandState, false); }; }, [copyContextStoreStates], ); return { openRootCommandMenu, closeCommandMenu, onCommandMenuCloseAnimationComplete, navigateCommandMenu, navigateCommandMenuHistory, goBackFromCommandMenu, openRecordsSearchPage, openRecordInCommandMenu, toggleCommandMenu, setGlobalCommandMenuContext, }; };