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 && (