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 62129beab..e86ac28e2 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx @@ -13,6 +13,10 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { Command, CommandType } from '@/command-menu/types/Command'; import { Company } from '@/companies/types/Company'; +import { + ContextStoreTargetedRecordsRule, + contextStoreTargetedRecordsRuleComponentState, +} from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -31,7 +35,12 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; import { useMemo, useRef } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { + useRecoilCallback, + useRecoilState, + useRecoilValue, + useSetRecoilState, +} from 'recoil'; import { Key } from 'ts-key-enum'; import { Avatar, IconNotes, IconSparkles, isDefined } from 'twenty-ui'; import { useDebounce } from 'use-debounce'; @@ -100,6 +109,25 @@ export const CommandMenu = () => { const commandMenuCommands = useRecoilValue(commandMenuCommandsState); const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu(); + const mainContextStoreComponentInstanceId = useRecoilValue( + mainContextStoreComponentInstanceIdState, + ); + + const setContextStoreTargetedRecordsRule = useRecoilCallback( + ({ set }) => + (rule: ContextStoreTargetedRecordsRule) => { + if (isDefined(mainContextStoreComponentInstanceId)) { + set( + contextStoreTargetedRecordsRuleComponentState.atomFamily({ + instanceId: mainContextStoreComponentInstanceId, + }), + rule, + ); + } + }, + [mainContextStoreComponentInstanceId], + ); + const isMobile = useIsMobile(); useScopedHotkeys( @@ -121,6 +149,23 @@ export const CommandMenu = () => { [closeCommandMenu], ); + useScopedHotkeys( + [Key.Backspace, Key.Delete], + () => { + if (!isNonEmptyString(commandMenuSearch)) { + setContextStoreTargetedRecordsRule({ + mode: 'selection', + selectedRecordIds: [], + }); + } + }, + AppHotkeyScope.CommandMenu, + [closeCommandMenu], + { + preventDefault: false, + }, + ); + const { loading: isPeopleLoading, records: people } = useSearchRecords({ skip: !isCommandMenuOpened, @@ -304,10 +349,6 @@ export const CommandMenu = () => { isOpportunitiesLoading || isCompaniesLoading; - const mainContextStoreComponentInstanceId = useRecoilValue( - mainContextStoreComponentInstanceIdState, - ); - return ( <> {isCommandMenuOpened && ( diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuCommandsEffect.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuCommandsEffect.tsx new file mode 100644 index 000000000..2a879b07e --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuCommandsEffect.tsx @@ -0,0 +1,29 @@ +import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; +import { commandMenuCommandsState } from '@/command-menu/states/commandMenuCommandsState'; +import { computeCommandMenuCommands } from '@/command-menu/utils/computeCommandMenuCommands'; +import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const CommandMenuCommandsEffect = () => { + const mainContextStoreComponentInstanceId = useRecoilValue( + mainContextStoreComponentInstanceIdState, + ); + + const actionMenuEntries = useRecoilComponentValueV2( + actionMenuEntriesComponentSelector, + mainContextStoreComponentInstanceId, + ); + + const setCommands = useSetRecoilState(commandMenuCommandsState); + + useEffect(() => { + if (isDefined(mainContextStoreComponentInstanceId)) { + setCommands(computeCommandMenuCommands(actionMenuEntries)); + } + }, [mainContextStoreComponentInstanceId, actionMenuEntries, setCommands]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordChip.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordChip.tsx index 68819f61d..911474b1b 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordChip.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordChip.tsx @@ -31,7 +31,7 @@ const StyledAvatarWrapper = styled.div` border-radius: ${({ theme }) => theme.border.radius.sm}; padding: ${({ theme }) => theme.spacing(0.5)}; border: 1px solid ${({ theme }) => theme.border.color.medium}; - &:not(:first-child) { + &:not(:first-of-type) { margin-left: -${({ theme }) => theme.spacing(1)}; } `; @@ -75,7 +75,7 @@ export const CommandMenuContextRecordChip = () => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, - mainContextStoreComponentInstanceId ?? undefined, + mainContextStoreComponentInstanceId, ); const { objectMetadataItem } = useObjectMetadataItemById({ @@ -83,7 +83,7 @@ export const CommandMenuContextRecordChip = () => { }); const { records, loading, totalCount } = useContextStoreSelectedRecords( - mainContextStoreComponentInstanceId ?? undefined, + mainContextStoreComponentInstanceId, ); if (loading || !totalCount) { @@ -96,6 +96,7 @@ export const CommandMenuContextRecordChip = () => { {records.map((record) => ( ))} 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 715333599..e472e4221 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -11,6 +11,7 @@ import { isDefined } from '~/utils/isDefined'; import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands'; +import { computeCommandMenuCommands } from '@/command-menu/utils/computeCommandMenuCommands'; import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons'; @@ -37,39 +38,17 @@ export const useCommandMenu = () => { ({ snapshot }) => () => { if (isDefined(mainContextStoreComponentInstanceId)) { - const actionMenuEntries = snapshot.getLoadable( - actionMenuEntriesComponentSelector.selectorFamily({ - instanceId: mainContextStoreComponentInstanceId, - }), - ); - - const commands = Object.values(COMMAND_MENU_COMMANDS); - - const actionCommands = actionMenuEntries - .getValue() - ?.filter((actionMenuEntry) => actionMenuEntry.type === 'standard') - ?.map((actionMenuEntry) => ({ - id: actionMenuEntry.key, - label: actionMenuEntry.label, - Icon: actionMenuEntry.Icon, - onCommandClick: actionMenuEntry.onClick, - type: CommandType.StandardAction, - })); - - const workflowRunCommands = actionMenuEntries - .getValue() - ?.filter( - (actionMenuEntry) => actionMenuEntry.type === 'workflow-run', + const actionMenuEntries = snapshot + .getLoadable( + actionMenuEntriesComponentSelector.selectorFamily({ + instanceId: mainContextStoreComponentInstanceId, + }), ) - ?.map((actionMenuEntry) => ({ - id: actionMenuEntry.key, - label: actionMenuEntry.label, - Icon: actionMenuEntry.Icon, - onCommandClick: actionMenuEntry.onClick, - type: CommandType.WorkflowRun, - })); + .getValue(); - setCommands([...commands, ...actionCommands, ...workflowRunCommands]); + const commands = computeCommandMenuCommands(actionMenuEntries); + + setCommands(commands); } setIsCommandMenuOpened(true); diff --git a/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts b/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts new file mode 100644 index 000000000..10e809f16 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts @@ -0,0 +1,31 @@ +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands'; +import { CommandType } from '@/command-menu/types/Command'; + +export const computeCommandMenuCommands = ( + actionMenuEntries: ActionMenuEntry[], +) => { + const commands = Object.values(COMMAND_MENU_COMMANDS); + + const actionCommands = actionMenuEntries + ?.filter((actionMenuEntry) => actionMenuEntry.type === 'standard') + ?.map((actionMenuEntry) => ({ + id: actionMenuEntry.key, + label: actionMenuEntry.label, + Icon: actionMenuEntry.Icon, + onCommandClick: actionMenuEntry.onClick, + type: CommandType.StandardAction, + })); + + const workflowRunCommands = actionMenuEntries + ?.filter((actionMenuEntry) => actionMenuEntry.type === 'workflow-run') + ?.map((actionMenuEntry) => ({ + id: actionMenuEntry.key, + label: actionMenuEntry.label, + Icon: actionMenuEntry.Icon, + onCommandClick: actionMenuEntry.onClick, + type: CommandType.WorkflowRun, + })); + + return [...commands, ...actionCommands, ...workflowRunCommands]; +}; diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreComponentInstanceIdSetterEffect.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreComponentInstanceIdSetterEffect.tsx index cc838bb7e..633203892 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreComponentInstanceIdSetterEffect.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreComponentInstanceIdSetterEffect.tsx @@ -11,10 +11,10 @@ export const MainContextStoreComponentInstanceIdSetterEffect = () => { const context = useContext(ContextStoreComponentInstanceContext); useEffect(() => { - setMainContextStoreComponentInstanceId(context?.instanceId ?? null); + setMainContextStoreComponentInstanceId(context?.instanceId ?? 'app'); return () => { - setMainContextStoreComponentInstanceId(null); + setMainContextStoreComponentInstanceId('app'); }; }, [context, setMainContextStoreComponentInstanceId]); diff --git a/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts b/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts index 2e7343672..242b85e64 100644 --- a/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts +++ b/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts @@ -1,8 +1,6 @@ import { createState } from 'twenty-ui'; -export const mainContextStoreComponentInstanceIdState = createState< - string | null ->({ +export const mainContextStoreComponentInstanceIdState = createState({ key: 'mainContextStoreComponentInstanceIdState', - defaultValue: null, + defaultValue: 'app', }); diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx index 1c7d76480..6707ffcec 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx @@ -1,5 +1,6 @@ import { AuthModal } from '@/auth/components/AuthModal'; import { CommandMenu } from '@/command-menu/components/CommandMenu'; +import { CommandMenuCommandsEffect } from '@/command-menu/components/CommandMenuCommandsEffect'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu'; import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer'; @@ -80,6 +81,7 @@ export const DefaultLayout = () => { `} /> +