diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx index 1e521199b..17fafdde3 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx @@ -1,19 +1,24 @@ import { CommandGroup } from '@/command-menu/components/CommandGroup'; import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { ResetContextToSelectionCommandButton } from '@/command-menu/components/ResetContextToSelectionCommandButton'; import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight'; import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding'; import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick'; import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands'; +import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { Command } from '@/command-menu/types/Command'; +import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; +import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; @@ -60,6 +65,8 @@ export const CommandMenu = () => { const { t } = useLingui(); const { onItemClick } = useCommandMenuOnItemClick(); + const { resetPreviousCommandMenuContext } = + useResetPreviousCommandMenuContext(); const commandMenuSearch = useRecoilValue(commandMenuSearchState); @@ -84,22 +91,33 @@ export const CommandMenu = () => { commandMenuSearch, }); - const selectableItems = copilotCommands - .concat(matchingStandardActionRecordSelectionCommands) - .concat(matchingWorkflowRunRecordSelectionCommands) - .concat(matchingStandardActionGlobalCommands) - .concat(matchingWorkflowRunGlobalCommands) - .concat(matchingNavigateCommand) - .concat(peopleCommands) - .concat(companyCommands) - .concat(opportunityCommands) - .concat(noteCommands) - .concat(tasksCommands) - .concat(customObjectCommands) + const selectableItems: Command[] = copilotCommands + .concat( + matchingStandardActionRecordSelectionCommands, + matchingWorkflowRunRecordSelectionCommands, + matchingStandardActionGlobalCommands, + matchingWorkflowRunGlobalCommands, + matchingNavigateCommand, + peopleCommands, + companyCommands, + opportunityCommands, + noteCommands, + tasksCommands, + customObjectCommands, + ) .filter(isDefined); + const previousContextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( + contextStoreCurrentObjectMetadataIdComponentState, + 'command-menu-previous', + ); + const selectableItemIds = selectableItems.map((item) => item.id); + if (isNonEmptyString(previousContextStoreCurrentObjectMetadataId)) { + selectableItemIds.unshift('reset-context-to-selection'); + } + const commandGroups: CommandGroupConfig[] = [ { heading: t`Copilot`, @@ -168,6 +186,11 @@ export const CommandMenu = () => { selectableItemIdArray={selectableItemIds} hotkeyScope={AppHotkeyScope.CommandMenu} onEnter={(itemId) => { + if (itemId === 'reset-context-to-selection') { + resetPreviousCommandMenuContext(); + return; + } + const command = selectableItems.find( (item) => item.id === itemId, ); @@ -184,6 +207,16 @@ export const CommandMenu = () => { } }} > + {isNonEmptyString( + previousContextStoreCurrentObjectMetadataId, + ) && ( + + + + + + )} + {isNoResults && !isLoading && ( No results found )} diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextChip.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextChip.tsx index 119ed77ec..77a7e4616 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextChip.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextChip.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; -const StyledChip = styled.div` +const StyledChip = styled.div<{ variant?: 'default' | 'small' }>` align-items: center; background: ${({ theme }) => theme.background.transparent.light}; border: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -8,7 +8,8 @@ const StyledChip = styled.div` box-sizing: border-box; display: flex; gap: ${({ theme }) => theme.spacing(1)}; - height: ${({ theme }) => theme.spacing(8)}; + height: ${({ theme, variant }) => + variant === 'small' ? theme.spacing(6) : theme.spacing(8)}; padding: 0 ${({ theme }) => theme.spacing(2)}; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme }) => theme.font.weight.medium}; @@ -40,13 +41,15 @@ export const CommandMenuContextChip = ({ Icons, text, withIconBackground, + variant = 'default', }: { Icons: React.ReactNode[]; text?: string; withIconBackground?: boolean; + variant?: 'default' | 'small'; }) => { return ( - + {Icons.map((Icon, index) => ( { const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, @@ -17,6 +20,7 @@ export const CommandMenuContextRecordChip = ({ const { records, loading, totalCount } = useFindManyRecordsSelectedInContextStore({ limit: 3, + instanceId, }); if (loading || !totalCount) { @@ -31,17 +35,16 @@ export const CommandMenuContextRecordChip = ({ /> )); - const text = - totalCount === 1 - ? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] }) - .name - : `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`; - return ( ); }; diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx index e12233a8d..eb94ecc42 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx @@ -14,7 +14,10 @@ export const CommandMenuDefaultSelectionEffect = ({ const selectedItemId = useRecoilValue(selectedItemIdState); useEffect(() => { - if (isDefined(selectedItemId)) { + if ( + isDefined(selectedItemId) && + selectableItemIds.includes(selectedItemId) + ) { return; } diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx index 3907ccb47..5bb37d9e2 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx @@ -4,6 +4,7 @@ import { IconArrowUpRight, IconComponent, MenuItemCommand } from 'twenty-ui'; import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { ReactNode } from 'react'; export type CommandMenuItemProps = { label: string; @@ -14,6 +15,7 @@ export type CommandMenuItemProps = { firstHotKey?: string; secondHotKey?: string; shouldCloseCommandMenuOnClick?: boolean; + RightComponent?: ReactNode; }; export const CommandMenuItem = ({ @@ -25,6 +27,7 @@ export const CommandMenuItem = ({ firstHotKey, secondHotKey, shouldCloseCommandMenuOnClick, + RightComponent, }: CommandMenuItemProps) => { const { onItemClick } = useCommandMenuOnItemClick(); @@ -49,6 +52,7 @@ export const CommandMenuItem = ({ }) } isSelected={isSelectedItemId} + RightComponent={RightComponent} /> ); }; diff --git a/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx b/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx new file mode 100644 index 000000000..4c3412d56 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx @@ -0,0 +1,56 @@ +import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip'; +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext'; +import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { t } from '@lingui/core/macro'; +import { useRecoilValue } from 'recoil'; +import { IconArrowBackUp, isDefined } from 'twenty-ui'; + +export const ResetContextToSelectionCommandButton = () => { + const contextStoreTargetedRecordsRule = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + 'command-menu-previous', + ); + + const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( + contextStoreCurrentObjectMetadataIdComponentState, + 'command-menu-previous', + ); + + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const objectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === contextStoreCurrentObjectMetadataId, + ); + + const { resetPreviousCommandMenuContext } = + useResetPreviousCommandMenuContext(); + + if ( + !isDefined(objectMetadataItem) || + (contextStoreTargetedRecordsRule.mode === 'selection' && + contextStoreTargetedRecordsRule.selectedRecordIds.length === 0) + ) { + return null; + } + + return ( + + } + onClick={resetPreviousCommandMenuContext} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index a37a2b66a..7e65610c3 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -4,19 +4,18 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS 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 { isDefined } from '~/utils/isDefined'; -import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages'; +import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates'; +import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageTitle'; -import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; -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 { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent'; @@ -33,120 +32,22 @@ export const useCommandMenu = () => { mainContextStoreComponentInstanceIdState, ); + const { copyContextStoreStates } = useCopyContextStoreStates(); + const { resetContextStoreStates } = useResetContextStoreStates(); + const openCommandMenu = useRecoilCallback( - ({ snapshot, set }) => + ({ set }) => () => { - if (isDefined(mainContextStoreComponentInstanceId)) { - const contextStoreCurrentObjectMetadataId = snapshot - .getLoadable( - contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreCurrentObjectMetadataId, - ); - - const contextStoreTargetedRecordsRule = snapshot - .getLoadable( - contextStoreTargetedRecordsRuleComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreTargetedRecordsRuleComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreTargetedRecordsRule, - ); - - const contextStoreNumberOfSelectedRecords = snapshot - .getLoadable( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreNumberOfSelectedRecords, - ); - - const contextStoreFilters = snapshot - .getLoadable( - contextStoreFiltersComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreFiltersComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreFilters, - ); - - const contextStoreCurrentViewId = snapshot - .getLoadable( - contextStoreCurrentViewIdComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreCurrentViewIdComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreCurrentViewId, - ); - - const contextStoreCurrentViewType = snapshot - .getLoadable( - contextStoreCurrentViewTypeComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - contextStoreCurrentViewTypeComponentState.atomFamily({ - instanceId: 'command-menu', - }), - contextStoreCurrentViewType, - ); - } - - const actionMenuEntries = snapshot - .getLoadable( - actionMenuEntriesComponentState.atomFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ) - .getValue(); - - set( - actionMenuEntriesComponentState.atomFamily({ - instanceId: 'command-menu', - }), - actionMenuEntries, - ); + copyContextStoreStates({ + instanceIdToCopyFrom: mainContextStoreComponentInstanceId, + instanceIdToCopyTo: 'command-menu', + }); set(isCommandMenuOpenedState, true); setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen); }, [ + copyContextStoreStates, mainContextStoreComponentInstanceId, setHotkeyScopeAndMemorizePreviousScope, ], @@ -160,57 +61,8 @@ export const useCommandMenu = () => { .getValue(); if (isCommandMenuOpened) { - set( - contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ - instanceId: 'command-menu', - }), - null, - ); - - set( - contextStoreTargetedRecordsRuleComponentState.atomFamily({ - instanceId: 'command-menu', - }), - { - mode: 'selection', - selectedRecordIds: [], - }, - ); - - set( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: 'command-menu', - }), - 0, - ); - - set( - contextStoreFiltersComponentState.atomFamily({ - instanceId: 'command-menu', - }), - [], - ); - - set( - contextStoreCurrentViewIdComponentState.atomFamily({ - instanceId: 'command-menu', - }), - null, - ); - - set( - contextStoreCurrentViewTypeComponentState.atomFamily({ - instanceId: 'command-menu', - }), - null, - ); - - set( - actionMenuEntriesComponentState.atomFamily({ - instanceId: 'command-menu', - }), - new Map(), - ); + resetContextStoreStates('command-menu'); + resetContextStoreStates('command-menu-previous'); set(viewableRecordIdState, null); set(commandMenuPageState, CommandMenuPages.Root); @@ -225,7 +77,7 @@ export const useCommandMenu = () => { emitRightDrawerCloseEvent(); } }, - [goBackToPreviousHotkeyScope, resetSelectedItem], + [goBackToPreviousHotkeyScope, resetContextStoreStates, resetSelectedItem], ); const toggleCommandMenu = useRecoilCallback( @@ -258,44 +110,59 @@ export const useCommandMenu = () => { [openCommandMenu], ); - const setGlobalCommandMenuContext = useRecoilCallback(({ set }) => { - return () => { - set( - contextStoreTargetedRecordsRuleComponentState.atomFamily({ - instanceId: 'command-menu', - }), - { - mode: 'selection', - selectedRecordIds: [], - }, - ); + const setGlobalCommandMenuContext = useRecoilCallback( + ({ set }) => { + return () => { + copyContextStoreStates({ + instanceIdToCopyFrom: 'command-menu', + instanceIdToCopyTo: 'command-menu-previous', + }); - set( - contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ - instanceId: 'command-menu', - }), - 0, - ); + set( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: 'command-menu', + }), + { + mode: 'selection', + selectedRecordIds: [], + }, + ); - set( - contextStoreCurrentViewTypeComponentState.atomFamily({ - instanceId: 'command-menu', - }), - null, - ); + set( + contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ + instanceId: 'command-menu', + }), + 0, + ); - set(commandMenuPageInfoState, { - title: undefined, - Icon: undefined, - }); - }; - }, []); + set( + contextStoreFiltersComponentState.atomFamily({ + instanceId: 'command-menu', + }), + [], + ); + + set( + contextStoreCurrentViewTypeComponentState.atomFamily({ + instanceId: 'command-menu', + }), + ContextStoreViewType.Table, + ); + + set(commandMenuPageInfoState, { + title: undefined, + Icon: undefined, + }); + }; + }, + [copyContextStoreStates], + ); return { openCommandMenu, closeCommandMenu, openRecordInCommandMenu, toggleCommandMenu, - resetCommandMenuContext: setGlobalCommandMenuContext, + setGlobalCommandMenuContext, }; }; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts index ccc39f699..c8c6e32a4 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts @@ -2,15 +2,17 @@ import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; +import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; export const useCommandMenuHotKeys = () => { - const { closeCommandMenu, toggleCommandMenu, resetCommandMenuContext } = + const { closeCommandMenu, toggleCommandMenu, setGlobalCommandMenuContext } = useCommandMenu(); const commandMenuSearch = useRecoilValue(commandMenuSearchState); @@ -19,6 +21,11 @@ export const useCommandMenuHotKeys = () => { const commandMenuPage = useRecoilValue(commandMenuPageState); + const contextStoreTargetedRecordsRuleComponent = useRecoilComponentValueV2( + contextStoreTargetedRecordsRuleComponentState, + 'command-menu', + ); + useScopedHotkeys( 'ctrl+k,meta+k', () => { @@ -43,9 +50,14 @@ export const useCommandMenuHotKeys = () => { () => { if ( commandMenuPage === CommandMenuPages.Root && - !isNonEmptyString(commandMenuSearch) + !isNonEmptyString(commandMenuSearch) && + !( + contextStoreTargetedRecordsRuleComponent.mode === 'selection' && + contextStoreTargetedRecordsRuleComponent.selectedRecordIds.length === + 0 + ) ) { - resetCommandMenuContext(); + setGlobalCommandMenuContext(); } }, AppHotkeyScope.CommandMenuOpen, diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCopyContextStoreAndActionMenuStates.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCopyContextStoreAndActionMenuStates.ts new file mode 100644 index 000000000..61e767092 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCopyContextStoreAndActionMenuStates.ts @@ -0,0 +1,129 @@ +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; +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 { useRecoilCallback } from 'recoil'; + +export const useCopyContextStoreStates = () => { + const copyContextStoreStates = useRecoilCallback( + ({ snapshot, set }) => + ({ + instanceIdToCopyFrom, + instanceIdToCopyTo, + }: { + instanceIdToCopyFrom: string; + instanceIdToCopyTo: string; + }) => { + const contextStoreCurrentObjectMetadataId = snapshot + .getLoadable( + contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreCurrentObjectMetadataId, + ); + + const contextStoreTargetedRecordsRule = snapshot + .getLoadable( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreTargetedRecordsRule, + ); + + const contextStoreNumberOfSelectedRecords = snapshot + .getLoadable( + contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreNumberOfSelectedRecords, + ); + + const contextStoreFilters = snapshot + .getLoadable( + contextStoreFiltersComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreFiltersComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreFilters, + ); + + const contextStoreCurrentViewId = snapshot + .getLoadable( + contextStoreCurrentViewIdComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreCurrentViewIdComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreCurrentViewId, + ); + + const contextStoreCurrentViewType = snapshot + .getLoadable( + contextStoreCurrentViewTypeComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + contextStoreCurrentViewTypeComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + contextStoreCurrentViewType, + ); + + const actionMenuEntries = snapshot + .getLoadable( + actionMenuEntriesComponentState.atomFamily({ + instanceId: instanceIdToCopyFrom, + }), + ) + .getValue(); + + set( + actionMenuEntriesComponentState.atomFamily({ + instanceId: instanceIdToCopyTo, + }), + actionMenuEntries, + ); + }, + [], + ); + + return { copyContextStoreStates }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useResetContextStoreStates.ts b/packages/twenty-front/src/modules/command-menu/hooks/useResetContextStoreStates.ts new file mode 100644 index 000000000..795bb1253 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/hooks/useResetContextStoreStates.ts @@ -0,0 +1,68 @@ +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; +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 { useRecoilCallback } from 'recoil'; + +export const useResetContextStoreStates = () => { + const resetContextStoreStates = useRecoilCallback(({ set }) => { + return (instanceId: string) => { + set( + contextStoreCurrentObjectMetadataIdComponentState.atomFamily({ + instanceId, + }), + null, + ); + + set( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId, + }), + { + mode: 'selection', + selectedRecordIds: [], + }, + ); + + set( + contextStoreNumberOfSelectedRecordsComponentState.atomFamily({ + instanceId, + }), + 0, + ); + + set( + contextStoreFiltersComponentState.atomFamily({ + instanceId, + }), + [], + ); + + set( + contextStoreCurrentViewIdComponentState.atomFamily({ + instanceId, + }), + null, + ); + + set( + contextStoreCurrentViewTypeComponentState.atomFamily({ + instanceId, + }), + null, + ); + + set( + actionMenuEntriesComponentState.atomFamily({ + instanceId, + }), + new Map(), + ); + }; + }, []); + + return { resetContextStoreStates }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useResetPreviousCommandMenuContext.ts b/packages/twenty-front/src/modules/command-menu/hooks/useResetPreviousCommandMenuContext.ts new file mode 100644 index 000000000..197209eeb --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/hooks/useResetPreviousCommandMenuContext.ts @@ -0,0 +1,19 @@ +import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates'; +import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates'; + +export const useResetPreviousCommandMenuContext = () => { + const { copyContextStoreStates } = useCopyContextStoreStates(); + const { resetContextStoreStates } = useResetContextStoreStates(); + + const resetPreviousCommandMenuContext = () => { + copyContextStoreStates({ + instanceIdToCopyFrom: 'command-menu-previous', + instanceIdToCopyTo: 'command-menu', + }); + resetContextStoreStates('command-menu-previous'); + }; + + return { + resetPreviousCommandMenuContext, + }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/utils/getRecordContextText.ts b/packages/twenty-front/src/modules/command-menu/utils/getRecordContextText.ts new file mode 100644 index 000000000..a2ece251b --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/utils/getRecordContextText.ts @@ -0,0 +1,14 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { capitalize } from 'twenty-shared'; + +export const getSelectedRecordsContextText = ( + objectMetadataItem: ObjectMetadataItem, + records: ObjectRecord[], + totalCount: number, +) => { + return totalCount === 1 + ? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] }).name + : `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`; +}; diff --git a/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts b/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts index 5743292a5..5a7b25bfb 100644 --- a/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts +++ b/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts @@ -53,6 +53,7 @@ export const useFindManyRecordsSelectedInContextStore = ({ contextStoreTargetedRecordsRule.mode === 'selection' && contextStoreTargetedRecordsRule.selectedRecordIds.length === 0, limit, + fetchPolicy: 'cache-and-network', }); return { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index b72f7ed25..30dd397b1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -39,6 +39,7 @@ export const useFindManyRecords = ({ const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); + const { findManyRecordsQuery } = useFindManyRecordsQuery({ objectNameSingular, recordGqlFields, diff --git a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemCommand.tsx b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemCommand.tsx index 329909517..6f38ab56f 100644 --- a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemCommand.tsx +++ b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemCommand.tsx @@ -8,6 +8,7 @@ import { import { IconComponent } from '@ui/display'; import { useIsMobile } from '@ui/utilities/responsive/hooks/useIsMobile'; +import { ReactNode } from 'react'; import { MenuItemCommandHotKeys } from './MenuItemCommandHotKeys'; const StyledMenuItemLabelText = styled(StyledMenuItemLabel)` @@ -72,6 +73,7 @@ export type MenuItemCommandProps = { className?: string; isSelected?: boolean; onClick?: () => void; + RightComponent?: ReactNode; }; export const MenuItemCommand = ({ @@ -82,6 +84,7 @@ export const MenuItemCommand = ({ className, isSelected, onClick, + RightComponent, }: MenuItemCommandProps) => { const theme = useTheme(); const isMobile = useIsMobile(); @@ -99,6 +102,7 @@ export const MenuItemCommand = ({ )} {text} + {RightComponent} {!isMobile && (