## Recoil to Jotai Migration — PR 13: Workflow, Page Layout, AI,
Activities, Settings, SSE & Remaining Modules

### Summary

This is the 13th PR in the [14-PR migration
series](packages/twenty-front/src/modules/ui/utilities/state/jotai/MIGRATION_PLAN.md)
to replace Recoil with Jotai as the state management library in
`twenty-front`. This PR migrates the remaining consumer code across all
modules — updating ~1,076 files with ~10k insertions/deletions.

### What changed

**New Jotai infrastructure (13 new files):**
- `createComponentSelectorV2` and `createComponentFamilySelectorV2` —
instance-scoped derived atoms, replacing Recoil's component selectors
- 6 new hooks: `useRecoilComponentSelectorValueV2`,
`useRecoilComponentFamilySelectorValueV2`,
`useRecoilComponentSelectorCallbackStateV2`,
`useRecoilComponentFamilySelectorCallbackStateV2`,
`useRecoilComponentStateCallbackStateV2`,
`useRecoilComponentFamilyStateCallbackStateV2`
- New types: `ComponentSelectorV2`, `ComponentFamilySelectorV2`,
`SelectorCallbacksV2`
- Extended `buildGetHelper` to support the new selector patterns

**State definition migrations:**
- `createState()` → `createStateV2()` (Jotai `atom()`)
- `createFamilyState()` → `createFamilyStateV2()` (Jotai `atom()` with
family cache)
- Recoil `selector`/`selectorFamily` → Jotai derived `atom()` via V2
utilities

**Hook migrations (~2,400 removed, ~1,545 added V2 equivalents):**
- `useRecoilValue` → `useRecoilValueV2`
- `useRecoilState` → `useRecoilStateV2`
- `useSetRecoilState` → `useSetRecoilStateV2`
- `useRecoilCallback` → `useCallback` + `jotaiStore.get/set`
- `useRecoilComponentValue` → `useRecoilComponentValueV2`
- `useSetRecoilComponentState` → `useSetRecoilComponentStateV2`
- `useRecoilComponentFamilyValue` → `useRecoilComponentFamilyValueV2` /
`useFamilySelectorValueV2`
- ~397 direct `import from 'recoil'` removed

**Deleted files (9 files):**
- `dateLocaleState.ts` — consolidated
- `currentAIChatThreadTitleStateV2.ts` — renamed to replace the original
- `isCommandMenuOpenedState.ts` — removed
- `filesFieldUploadState.ts` — replaced by V2
- `recordStoreFamilySelector.ts`, `recordStoreFieldValueSelector.ts`,
`recordStoreRecordsSelector.ts` — replaced by V2 equivalents
- `settingsAllRolesSelector.ts` — replaced by `useSettingsAllRoles` hook
- `settingsRoleIdsStateV2.ts` — consolidated

**Modules migrated:**
- Workflow (diagram, steps, variables, filters)
- Page Layout (widgets, fields, graph, tabs)
- AI (chat, agents, browsing context)
- Activities (rich text editor, targets, timeline)
- Action Menu (single/multiple record actions)
- Command Menu (navigation, history, pages, workflow)
- Context Store
- Record Board, Record Table, Record Calendar, Record Index
- Record Field, Record Filter, Record Sort, Record Group
- Record Drag, Record Picker, Record Store
- Views (view bar, view picker, filters, sorts)
- Settings (roles, permissions, SSO, security, data model, developers,
domains)
- SSE (event streams, subscriptions)
- Spreadsheet Import
- Auth, Favorites, Front Components, Information Banner
This commit is contained in:
Charles Bochet
2026-02-24 17:37:01 +01:00
committed by GitHub
parent c6ec764b23
commit 0f2af6a6cb
1326 changed files with 19248 additions and 19389 deletions
@@ -1,13 +1,14 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconDotsVertical } from 'twenty-ui/display';
@@ -26,19 +27,9 @@ const renderHooks = () => {
const { result } = renderHook(
() => {
const commandMenu = useCommandMenu();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
return {
commandMenu,
isCommandMenuOpened,
commandMenuNavigationStack,
commandMenuPage,
commandMenuPageInfo,
};
},
{
@@ -60,31 +51,31 @@ describe('useCommandMenu', () => {
result.current.commandMenu.openCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
act(() => {
result.current.commandMenu.closeCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
});
it('should toggle the command menu', () => {
const { result } = renderHooks();
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
});
it('should navigate command menu and reset navigation stack when resetNavigationStack is true', () => {
@@ -99,9 +90,15 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo.title).toBe('First Page');
expect(result.current.commandMenuNavigationStack).toHaveLength(1);
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom).title).toBe(
'First Page',
);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
1,
);
act(() => {
result.current.commandMenu.navigateCommandMenu({
@@ -112,7 +109,9 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuNavigationStack).toHaveLength(2);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
2,
);
act(() => {
result.current.commandMenu.navigateCommandMenu({
@@ -123,8 +122,14 @@ describe('useCommandMenu', () => {
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo.title).toBe('Reset Page');
expect(result.current.commandMenuNavigationStack).toHaveLength(1);
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom).title).toBe(
'Reset Page',
);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toHaveLength(
1,
);
});
});
@@ -1,21 +1,23 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
import { RecoilRoot } 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 { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconList } from 'twenty-ui/display';
@@ -65,62 +67,13 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const viewableRecordId = useRecoilValueV2(viewableRecordIdState);
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
commandMenuNavigationMorphItemsByPageState,
);
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 setHasUserSelectedCommand = useSetRecoilState(
hasUserSelectedCommandState,
);
const setIsCommandMenuClosing = useSetRecoilState(
isCommandMenuClosingState,
);
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
const setViewableRecordId = useSetRecoilStateV2(viewableRecordIdState);
return {
commandMenuCloseAnimationCompleteCleanup,
commandMenuPage,
commandMenuPageInfo,
isCommandMenuOpened,
commandMenuSearch,
commandMenuNavigationStack,
commandMenuNavigationMorphItemsByPage,
hasUserSelectedCommand,
isCommandMenuClosing,
viewableRecordId,
setCommandMenuPage,
setCommandMenuPageInfo,
setIsCommandMenuOpened,
setCommandMenuSearch,
setCommandMenuNavigationStack,
setHasUserSelectedCommand,
setIsCommandMenuClosing,
setViewableRecordId,
};
},
@@ -135,15 +88,15 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
const { result } = renderHooks();
act(() => {
result.current.setCommandMenuPage(CommandMenuPages.ViewRecord);
result.current.setCommandMenuPageInfo({
jotaiStore.set(commandMenuPageState.atom, CommandMenuPages.ViewRecord);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: 'Test Record',
Icon: IconList,
instanceId: 'test-id',
});
result.current.setIsCommandMenuOpened(true);
result.current.setCommandMenuSearch('test search');
result.current.setCommandMenuNavigationStack([
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(commandMenuSearchState.atom, 'test search');
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -151,20 +104,22 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
pageId: '1',
},
]);
result.current.setHasUserSelectedCommand(true);
result.current.setIsCommandMenuClosing(true);
jotaiStore.set(hasUserSelectedCommandState.atom, true);
jotaiStore.set(isCommandMenuClosingState.atom, true);
result.current.setViewableRecordId('record-123');
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.ViewRecord);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.ViewRecord,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).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([
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('test search');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -172,25 +127,27 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
pageId: '1',
},
]);
expect(result.current.hasUserSelectedCommand).toBe(true);
expect(result.current.isCommandMenuClosing).toBe(true);
expect(jotaiStore.get(hasUserSelectedCommandState.atom)).toBe(true);
expect(jotaiStore.get(isCommandMenuClosingState.atom)).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({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).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.hasUserSelectedCommand).toBe(false);
expect(result.current.isCommandMenuClosing).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([]);
expect(jotaiStore.get(hasUserSelectedCommandState.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuClosingState.atom)).toBe(false);
expect(result.current.viewableRecordId).toBe(null);
});
@@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
@@ -9,7 +9,8 @@ import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistor
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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconList, IconSearch } from 'twenty-ui/display';
@@ -26,20 +27,10 @@ const renderHooks = () => {
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,
};
},
@@ -72,7 +63,7 @@ describe('useCommandMenuHistory', () => {
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -91,7 +82,7 @@ describe('useCommandMenuHistory', () => {
result.current.commandMenuHistory.goBackFromCommandMenu();
});
expect(result.current.commandMenuNavigationStack).toEqual([
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
page: CommandMenuPages.SearchRecords,
pageTitle: 'Search',
@@ -99,8 +90,10 @@ describe('useCommandMenuHistory', () => {
pageId: '1',
},
]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.SearchRecords,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
@@ -111,14 +104,16 @@ describe('useCommandMenuHistory', () => {
result.current.commandMenuCloseAnimationCompleteCleanup.commandMenuCloseAnimationCompleteCleanup();
});
expect(result.current.commandMenuNavigationStack).toEqual([]);
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([]);
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).toEqual({
title: undefined,
instanceId: '',
Icon: undefined,
});
expect(result.current.isCommandMenuOpened).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
});
it('should navigate to a page in history', () => {
@@ -137,8 +132,10 @@ describe('useCommandMenuHistory', () => {
result.current.commandMenuHistory.navigateCommandMenuHistory(0);
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.SearchRecords);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.SearchRecords,
);
expect(jotaiStore.get(commandMenuPageInfoState.atom)).toEqual({
title: 'Search',
Icon: IconSearch,
instanceId: '1',
@@ -1,8 +1,7 @@
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -21,11 +20,8 @@ const renderHooks = () => {
() => {
const { onItemClick } = useCommandMenuOnItemClick();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
return {
onItemClick,
isCommandMenuOpened,
};
},
{
@@ -1,59 +1,45 @@
import { useCommandMenuUpdateNavigationMorphItemsByPage } from '@/command-menu/hooks/useCommandMenuUpdateNavigationMorphItemsByPage';
import { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
const pageId = 'merge-page-id';
const objectMetadataId = 'company-metadata-id';
const Wrapper = ({
children,
initialRecordIds,
}: {
children: React.ReactNode;
initialRecordIds: string[];
}) => (
<RecoilRoot
initializeState={({ set }) => {
set(
commandMenuNavigationMorphItemsByPageState,
new Map([
[
pageId,
initialRecordIds.map((recordId) => ({
objectMetadataId,
recordId,
})),
],
]),
);
}}
>
{children}
</RecoilRoot>
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
);
const renderHooks = (initialRecordIds: string[]) =>
renderHook(
const renderHooks = (initialRecordIds: string[]) => {
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
new Map([
[
pageId,
initialRecordIds.map((recordId) => ({
objectMetadataId,
recordId,
})),
],
]),
);
return renderHook(
() => {
const { updateCommandMenuNavigationMorphItemsByPage } =
useCommandMenuUpdateNavigationMorphItemsByPage();
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
commandMenuNavigationMorphItemsByPageState,
);
return {
updateCommandMenuNavigationMorphItemsByPage,
commandMenuNavigationMorphItemsByPage,
};
},
{
wrapper: ({ children }) => (
<Wrapper initialRecordIds={initialRecordIds}>{children}</Wrapper>
),
wrapper: Wrapper,
},
);
};
describe('useCommandMenuUpdateNavigationMorphItemsByPage', () => {
it('should replace existing items for a page instead of appending', async () => {
@@ -68,7 +54,9 @@ describe('useCommandMenuUpdateNavigationMorphItemsByPage', () => {
});
expect(
result.current.commandMenuNavigationMorphItemsByPage.get(pageId),
jotaiStore
.get(commandMenuNavigationMorphItemsByPageState.atom)
.get(pageId),
).toEqual([
{
objectMetadataId,
@@ -98,7 +86,9 @@ describe('useCommandMenuUpdateNavigationMorphItemsByPage', () => {
});
expect(
result.current.commandMenuNavigationMorphItemsByPage.get(pageId),
jotaiStore
.get(commandMenuNavigationMorphItemsByPageState.atom)
.get(pageId),
).toEqual([
{
objectMetadataId,
@@ -1,12 +1,12 @@
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 { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { CommandMenuPages } from 'twenty-shared/types';
import { Icon123, useIcons } from 'twenty-ui/display';
@@ -40,19 +40,10 @@ const renderHooks = () => {
() => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const { getIcon } = useIcons();
return {
navigateCommandMenu,
commandMenuPage,
commandMenuNavigationStack,
commandMenuPageInfo,
getIcon,
};
},
@@ -82,8 +73,10 @@ describe('useNavigateCommandMenu', () => {
});
});
expect(result.current.commandMenuPage).toBe(CommandMenuPages.Root);
expect(result.current.commandMenuNavigationStack).toEqual([
expect(jotaiStore.get(commandMenuPageState.atom)).toBe(
CommandMenuPages.Root,
);
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
page: CommandMenuPages.Root,
pageTitle: 'Root',
@@ -92,7 +85,7 @@ describe('useNavigateCommandMenu', () => {
pageId: 'mocked-uuid',
},
]);
expect(result.current.commandMenuPageInfo).toEqual({
expect(jotaiStore.get(commandMenuPageInfoState.atom)).toEqual({
title: 'Root',
Icon: Icon123,
instanceId: 'mocked-uuid',
@@ -5,7 +5,7 @@ import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/Com
import { useOpenCalendarEventInCommandMenu } from '@/command-menu/hooks/useOpenCalendarEventInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconCalendarEvent } from 'twenty-ui/display';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
@@ -46,7 +46,7 @@ const renderHooks = () => {
const { openCalendarEventInCommandMenu } =
useOpenCalendarEventInCommandMenu();
const viewableRecordId = useRecoilComponentValue(
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
@@ -5,7 +5,7 @@ import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/Com
import { useOpenEmailThreadInCommandMenu } from '@/command-menu/hooks/useOpenEmailThreadInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconMail } from 'twenty-ui/display';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
@@ -46,7 +46,7 @@ const renderHooks = () => {
const { openEmailThreadInCommandMenu } =
useOpenEmailThreadInCommandMenu();
const viewableRecordId = useRecoilComponentValue(
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
@@ -1,19 +1,18 @@
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 { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { CommandMenuPages } from 'twenty-shared/types';
import { useIcons } from 'twenty-ui/display';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
@@ -53,32 +52,27 @@ const renderHooks = () => {
() => {
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
commandMenuNavigationMorphItemsByPageState,
);
const viewableRecordId = useRecoilComponentValue(
const viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
const viewableRecordNameSingular = useRecoilComponentValue(
const viewableRecordNameSingular = useRecoilComponentValueV2(
viewableRecordNameSingularComponentState,
'mocked-uuid',
);
const currentObjectMetadataItemId = useRecoilComponentValue(
const currentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'mocked-uuid',
);
const targetedRecordsRule = useRecoilComponentValue(
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
'mocked-uuid',
);
const numberOfSelectedRecords = useRecoilComponentValue(
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
'mocked-uuid',
);
const currentViewType = useRecoilComponentValue(
const currentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
'mocked-uuid',
);
@@ -87,8 +81,6 @@ const renderHooks = () => {
return {
openRecordInCommandMenu,
viewableRecordId,
commandMenuPage,
commandMenuNavigationMorphItemsByPage,
viewableRecordNameSingular,
currentObjectMetadataItemId,
targetedRecordsRule,
@@ -134,10 +126,11 @@ describe('useOpenRecordInCommandMenu', () => {
expect(result.current.numberOfSelectedRecords).toBe(1);
expect(result.current.currentViewType).toBe(ContextStoreViewType.ShowPage);
expect(result.current.commandMenuNavigationMorphItemsByPage.size).toBe(1);
expect(
result.current.commandMenuNavigationMorphItemsByPage.get('mocked-uuid'),
).toEqual([
const commandMenuNavigationMorphItemsByPage = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
expect(commandMenuNavigationMorphItemsByPage.size).toBe(1);
expect(commandMenuNavigationMorphItemsByPage.get('mocked-uuid')).toEqual([
{
objectMetadataId: personMockObjectMetadataItem.id,
recordId,
@@ -1,12 +1,12 @@
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 { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@@ -14,6 +14,7 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
@@ -47,9 +48,15 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
},
contextStoreNumberOfSelectedRecords: 2,
contextStoreCurrentViewType: ContextStoreViewType.Table,
onInitializeRecoilSnapshot: (snapshot) => {
snapshot.set(recordStoreFamilyState(peopleMock[0].id), peopleMock[0]);
snapshot.set(recordStoreFamilyState(peopleMock[1].id), peopleMock[1]);
onInitializeRecoilSnapshot: (_snapshot) => {
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[0].id),
peopleMock[0],
);
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[1].id),
peopleMock[1],
);
},
});
@@ -64,46 +71,34 @@ describe('useSetGlobalCommandMenuContext', () => {
const { setGlobalCommandMenuContext } =
useSetGlobalCommandMenuContext();
const targetedRecordsRule = useRecoilValue(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const numberOfSelectedRecords = useRecoilValue(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const filters = useRecoilValue(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
const filters = useRecoilComponentValueV2(
contextStoreFiltersComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const filterGroups = useRecoilValue(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
const filterGroups = useRecoilComponentValueV2(
contextStoreFilterGroupsComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const anyFieldFilterValue = useRecoilValue(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
const anyFieldFilterValue = useRecoilComponentValueV2(
contextStoreAnyFieldFilterValueComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
const currentViewType = useRecoilValue(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const hasUserSelectedCommand = useRecoilValue(
hasUserSelectedCommandState,
const currentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
return {
@@ -113,8 +108,6 @@ describe('useSetGlobalCommandMenuContext', () => {
filters,
filterGroups,
currentViewType,
commandMenuPageInfo,
hasUserSelectedCommand,
anyFieldFilterValue,
};
},
@@ -131,12 +124,16 @@ describe('useSetGlobalCommandMenuContext', () => {
expect(result.current.filters).toEqual([]);
expect(result.current.anyFieldFilterValue).toEqual('');
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
expect(result.current.commandMenuPageInfo).toEqual({
const commandMenuPageInfo = jotaiStore.get(commandMenuPageInfoState.atom);
expect(commandMenuPageInfo).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
expect(result.current.hasUserSelectedCommand).toBe(false);
const hasUserSelectedCommand = jotaiStore.get(
hasUserSelectedCommandState.atom,
);
expect(hasUserSelectedCommand).toBe(false);
act(() => {
result.current.setGlobalCommandMenuContext();
@@ -150,12 +147,18 @@ describe('useSetGlobalCommandMenuContext', () => {
expect(result.current.filters).toEqual([]);
expect(result.current.anyFieldFilterValue).toEqual('');
expect(result.current.currentViewType).toBe(ContextStoreViewType.Table);
expect(result.current.commandMenuPageInfo).toEqual({
const commandMenuPageInfoAfter = jotaiStore.get(
commandMenuPageInfoState.atom,
);
expect(commandMenuPageInfoAfter).toEqual({
title: undefined,
Icon: undefined,
instanceId: '',
});
expect(result.current.hasUserSelectedCommand).toBe(false);
const hasUserSelectedCommandAfter = jotaiStore.get(
hasUserSelectedCommandState.atom,
);
expect(hasUserSelectedCommandAfter).toBe(false);
});
it('should call copyContextStoreStates with correct parameters', () => {
@@ -1,9 +1,10 @@
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { RecoilRoot } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconArrowDown, IconDotsVertical } from 'twenty-ui/display';
@@ -23,30 +24,22 @@ const mockedNavigationStack = [
];
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot
initializeState={({ set }) => {
set(commandMenuNavigationStackState, mockedNavigationStack);
set(commandMenuPageInfoState, mockedPageInfo);
}}
>
{children}
</RecoilRoot>
<RecoilRoot>{children}</RecoilRoot>
);
describe('useUpdateCommandMenuPageInfo', () => {
beforeEach(() => {
jotaiStore.set(commandMenuNavigationStackState.atom, mockedNavigationStack);
jotaiStore.set(commandMenuPageInfoState.atom, mockedPageInfo);
});
const renderHooks = () => {
const { result } = renderHook(
() => {
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
const commandMenuNavigationStack = useRecoilValue(
commandMenuNavigationStackState,
);
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
return {
updateCommandMenuPageInfo,
commandMenuNavigationStack,
commandMenuPageInfo,
};
},
{ wrapper: Wrapper },
@@ -67,7 +60,10 @@ describe('useUpdateCommandMenuPageInfo', () => {
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
const commandMenuNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
expect(commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.Root,
pageTitle: 'New Title',
@@ -76,7 +72,8 @@ describe('useUpdateCommandMenuPageInfo', () => {
},
]);
expect(result.current.commandMenuPageInfo).toEqual({
const commandMenuPageInfo = jotaiStore.get(commandMenuPageInfoState.atom);
expect(commandMenuPageInfo).toEqual({
title: 'New Title',
Icon: IconArrowDown,
instanceId: 'test-instance',
@@ -92,7 +89,10 @@ describe('useUpdateCommandMenuPageInfo', () => {
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
const commandMenuNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
expect(commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.Root,
pageTitle: 'New Title',
@@ -101,7 +101,8 @@ describe('useUpdateCommandMenuPageInfo', () => {
},
]);
expect(result.current.commandMenuPageInfo).toEqual({
const commandMenuPageInfo = jotaiStore.get(commandMenuPageInfoState.atom);
expect(commandMenuPageInfo).toEqual({
title: 'New Title',
Icon: IconDotsVertical,
instanceId: 'test-instance',
@@ -117,7 +118,10 @@ describe('useUpdateCommandMenuPageInfo', () => {
});
});
expect(result.current.commandMenuNavigationStack).toEqual([
const commandMenuNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
expect(commandMenuNavigationStack).toEqual([
{
page: CommandMenuPages.Root,
pageTitle: 'Initial Title',
@@ -126,7 +130,8 @@ describe('useUpdateCommandMenuPageInfo', () => {
},
]);
expect(result.current.commandMenuPageInfo).toEqual({
const commandMenuPageInfo = jotaiStore.get(commandMenuPageInfoState.atom);
expect(commandMenuPageInfo).toEqual({
title: 'Initial Title',
Icon: IconArrowDown,
instanceId: 'test-instance',
@@ -1,5 +1,4 @@
import { renderHook } from '@testing-library/react';
import { useRecoilValue } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
@@ -7,14 +6,12 @@ import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { commandMenuWorkflowIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowIdComponentState';
import { commandMenuWorkflowVersionIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState';
import { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import { act } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
@@ -67,40 +64,36 @@ const renderHooks = () => {
openWorkflowEditStepTypeInCommandMenu,
openWorkflowViewStepInCommandMenu,
} = useWorkflowCommandMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
commandMenuNavigationMorphItemsByPageState,
);
const viewableRecordId = useRecoilComponentValue(
const _viewableRecordId = useRecoilComponentValueV2(
viewableRecordIdComponentState,
'mocked-uuid',
);
const viewableRecordNameSingular = useRecoilComponentValue(
const viewableRecordNameSingular = useRecoilComponentValueV2(
viewableRecordNameSingularComponentState,
'mocked-uuid',
);
const currentObjectMetadataItemId = useRecoilComponentValue(
const currentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
'mocked-uuid',
);
const targetedRecordsRule = useRecoilComponentValue(
const targetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
'mocked-uuid',
);
const numberOfSelectedRecords = useRecoilComponentValue(
const numberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
'mocked-uuid',
);
const currentViewType = useRecoilComponentValue(
const currentViewType = useRecoilComponentValueV2(
contextStoreCurrentViewTypeComponentState,
'mocked-uuid',
);
const workflowId = useRecoilComponentValue(
const workflowId = useRecoilComponentValueV2(
commandMenuWorkflowIdComponentState,
'mocked-uuid',
);
const workflowVersionId = useRecoilComponentValue(
const workflowVersionId = useRecoilComponentValueV2(
commandMenuWorkflowVersionIdComponentState,
'mocked-uuid',
);
@@ -114,9 +107,6 @@ const renderHooks = () => {
openWorkflowViewStepInCommandMenu,
workflowId,
workflowVersionId,
viewableRecordId,
commandMenuPage,
commandMenuNavigationMorphItemsByPage,
viewableRecordNameSingular,
currentObjectMetadataItemId,
targetedRecordsRule,
@@ -1,17 +1,14 @@
import { useRecoilCallback } from 'recoil';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { t } from '@lingui/core/macro';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconDotsVertical } from 'twenty-ui/display';
@@ -23,28 +20,19 @@ export const useCommandMenu = () => {
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const store = useStore();
const closeCommandMenu = useCallback(() => {
const isCommandMenuOpened = jotaiStore.get(isCommandMenuOpenedStateV2.atom);
const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) =>
() => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
if (isCommandMenuOpened) {
store.set(addToNavPayloadRegistryStateV2.atom, new Map());
set(isCommandMenuOpenedState, false);
store.set(isCommandMenuOpenedStateV2.atom, false);
set(isCommandMenuClosingState, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID,
});
}
},
[closeAnyOpenDropdown, removeFocusItemFromFocusStackById, store],
);
if (isCommandMenuOpened) {
jotaiStore.set(addToNavPayloadRegistryStateV2.atom, new Map());
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
jotaiStore.set(isCommandMenuClosingState.atom, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID,
});
}
}, [closeAnyOpenDropdown, removeFocusItemFromFocusStackById]);
const openCommandMenu = useCallback(() => {
emitSidePanelOpenEvent();
@@ -57,23 +45,17 @@ export const useCommandMenu = () => {
});
}, [closeAnyOpenDropdown, navigateCommandMenu]);
const toggleCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
async () => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
const toggleCommandMenu = useCallback(() => {
const isCommandMenuOpened = jotaiStore.get(isCommandMenuOpenedStateV2.atom);
set(commandMenuSearchState, '');
jotaiStore.set(commandMenuSearchState.atom, '');
if (isCommandMenuOpened) {
closeCommandMenu();
} else {
openCommandMenu();
}
},
[closeCommandMenu, openCommandMenu],
);
if (isCommandMenuOpened) {
closeCommandMenu();
} else {
openCommandMenu();
}
}, [closeCommandMenu, openCommandMenu]);
return {
openCommandMenu,
@@ -10,7 +10,6 @@ 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 { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
@@ -23,10 +22,10 @@ import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSide
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -39,107 +38,98 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
const { closeDropdown } = useCloseDropdown();
const store = useStore();
const commandMenuCloseAnimationCompleteCleanup = useCallback(() => {
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
({ snapshot, set }) =>
() => {
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
const currentPage = jotaiStore.get(commandMenuPageState.atom);
const currentPage = snapshot
.getLoadable(commandMenuPageState)
.getValue();
const isPageLayoutEditingPage =
currentPage === CommandMenuPages.PageLayoutWidgetTypeSelect ||
currentPage === CommandMenuPages.PageLayoutGraphTypeSelect ||
currentPage === CommandMenuPages.PageLayoutIframeSettings ||
currentPage === CommandMenuPages.PageLayoutTabSettings;
const isPageLayoutEditingPage =
currentPage === CommandMenuPages.PageLayoutWidgetTypeSelect ||
currentPage === CommandMenuPages.PageLayoutGraphTypeSelect ||
currentPage === CommandMenuPages.PageLayoutIframeSettings ||
currentPage === CommandMenuPages.PageLayoutTabSettings;
if (isPageLayoutEditingPage) {
const targetedRecordsRule = jotaiStore.get(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
);
if (isPageLayoutEditingPage) {
const targetedRecordsRule = snapshot
.getLoadable(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
)
.getValue();
if (
targetedRecordsRule.mode === 'selection' &&
targetedRecordsRule.selectedRecordIds.length === 1
) {
const recordId = targetedRecordsRule.selectedRecordIds[0];
const record = snapshot
.getLoadable(recordStoreFamilyState(recordId))
.getValue();
if (isDefined(record) && isDefined(record.pageLayoutId)) {
set(
pageLayoutEditingWidgetIdComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
set(
pageLayoutTabSettingsOpenTabIdComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
set(
pageLayoutDraggedAreaComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
}
}
}
set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(isCommandMenuOpenedState, false);
store.set(isCommandMenuOpenedStateV2.atom, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationMorphItemsByPageState, new Map());
set(commandMenuNavigationStackState, []);
resetSelectedItem();
set(hasUserSelectedCommandState, false);
emitSidePanelCloseEvent();
set(isCommandMenuClosingState, false);
store.set(
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
}),
WorkflowLogicFunctionTabId.CODE,
if (
targetedRecordsRule.mode === 'selection' &&
targetedRecordsRule.selectedRecordIds.length === 1
) {
const recordId = targetedRecordsRule.selectedRecordIds[0];
const record = jotaiStore.get(
recordStoreFamilyState.atomFamily(recordId),
);
for (const [pageId, morphItems] of snapshot
.getLoadable(commandMenuNavigationMorphItemsByPageState)
.getValue()) {
store.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId,
targetObjectId: morphItems[0].recordId,
}),
if (isDefined(record) && isDefined(record.pageLayoutId)) {
jotaiStore.set(
pageLayoutEditingWidgetIdComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
jotaiStore.set(
pageLayoutTabSettingsOpenTabIdComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
jotaiStore.set(
pageLayoutDraggedAreaComponentState.atomFamily({
instanceId: record.pageLayoutId,
}),
null,
);
}
},
[closeDropdown, resetContextStoreStates, resetSelectedItem, store],
);
}
}
jotaiStore.set(viewableRecordIdState.atom, null);
jotaiStore.set(commandMenuPageState.atom, CommandMenuPages.Root);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: undefined,
Icon: undefined,
instanceId: '',
});
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
jotaiStore.set(commandMenuSearchState.atom, '');
jotaiStore.set(commandMenuNavigationMorphItemsByPageState.atom, new Map());
jotaiStore.set(commandMenuNavigationStackState.atom, []);
resetSelectedItem();
jotaiStore.set(hasUserSelectedCommandState.atom, false);
emitSidePanelCloseEvent();
jotaiStore.set(isCommandMenuClosingState.atom, false);
jotaiStore.set(
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
}),
WorkflowLogicFunctionTabId.CODE,
);
const morphItemsByPage = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
for (const [pageId, morphItems] of morphItemsByPage) {
jotaiStore.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId,
targetObjectId: morphItems[0].recordId,
}),
}),
null,
);
}
}, [closeDropdown, resetContextStoreStates, resetSelectedItem]);
return {
commandMenuCloseAnimationCompleteCleanup,
@@ -5,11 +5,10 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { CommandMenuPages } from 'twenty-shared/types';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreIdentifiersFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifiersSelector';
import { recordStoreRecordsSelector } from '@/object-record/record-store/states/selectors/recordStoreRecordsSelector';
import { recordStoreRecordsSelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreRecordsSelectorV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons';
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
@@ -24,7 +23,7 @@ const StyledIconWrapper = styled.div`
`;
export const useCommandMenuContextChips = () => {
const commandMenuNavigationStack = useRecoilValue(
const commandMenuNavigationStack = useRecoilValueV2(
commandMenuNavigationStackState,
);
@@ -38,7 +37,7 @@ export const useCommandMenuContextChips = () => {
const theme = useTheme();
const commandMenuNavigationMorphItemsByPage = useRecoilValue(
const commandMenuNavigationMorphItemsByPage = useRecoilValueV2(
commandMenuNavigationMorphItemsByPageState,
);
@@ -55,11 +54,9 @@ export const useCommandMenuContextChips = () => {
allowRequestsToTwentyIcons,
},
);
const records = useRecoilValue(
recordStoreRecordsSelector({
recordIds: allRecordIds,
}),
);
const records = useFamilySelectorValueV2(recordStoreRecordsSelectorV2, {
recordIds: allRecordIds,
});
const contextChips = useMemo(() => {
const filteredCommandMenuNavigationStack =
@@ -1,4 +1,4 @@
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
@@ -8,132 +8,123 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useStore } from 'jotai';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { isNonEmptyArray } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
export const useCommandMenuHistory = () => {
const { closeCommandMenu } = useCommandMenu();
const store = useStore();
const goBackFromCommandMenu = useCallback(() => {
const currentNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
const goBackFromCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return () => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(0, -1);
const lastNavigationStackItem = newNavigationStack.at(-1);
const newNavigationStack = currentNavigationStack.slice(0, -1);
const lastNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(lastNavigationStackItem)) {
closeCommandMenu();
return;
}
if (!isDefined(lastNavigationStackItem)) {
closeCommandMenu();
return;
}
jotaiStore.set(commandMenuPageState.atom, lastNavigationStackItem.page);
set(commandMenuPageState, lastNavigationStackItem.page);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: lastNavigationStackItem.pageTitle,
Icon: lastNavigationStackItem.pageIcon,
instanceId: lastNavigationStackItem.pageId,
});
set(commandMenuPageInfoState, {
title: lastNavigationStackItem.pageTitle,
Icon: lastNavigationStackItem.pageIcon,
instanceId: lastNavigationStackItem.pageId,
});
jotaiStore.set(commandMenuNavigationStackState.atom, newNavigationStack);
set(commandMenuNavigationStackState, newNavigationStack);
const currentMorphItems = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemsByPageState)
.getValue();
if (currentNavigationStack.length > 0) {
const removedItem = currentNavigationStack.at(-1);
if (currentNavigationStack.length > 0) {
const removedItem = currentNavigationStack.at(-1);
if (isDefined(removedItem)) {
const newMorphItems = new Map(currentMorphItems);
newMorphItems.delete(removedItem.pageId);
set(commandMenuNavigationMorphItemsByPageState, newMorphItems);
const morphItems = currentMorphItems.get(removedItem.pageId);
if (isNonEmptyArray(morphItems)) {
store.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId: removedItem.pageId,
targetObjectId: morphItems[0].recordId,
}),
}),
null,
);
}
}
}
set(hasUserSelectedCommandState, false);
};
},
[closeCommandMenu, store],
);
const navigateCommandMenuHistory = useRecoilCallback(
({ snapshot, set }) => {
return (pageIndex: number) => {
const currentNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const newNavigationStack = currentNavigationStack.slice(
0,
pageIndex + 1,
if (isDefined(removedItem)) {
const newMorphItems = new Map(currentMorphItems);
newMorphItems.delete(removedItem.pageId);
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
newMorphItems,
);
set(commandMenuNavigationStackState, newNavigationStack);
const newNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(newNavigationStackItem)) {
throw new Error(
`No command menu navigation stack item found for index ${pageIndex}`,
const morphItems = currentMorphItems.get(removedItem.pageId);
if (isNonEmptyArray(morphItems)) {
jotaiStore.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId: removedItem.pageId,
targetObjectId: morphItems[0].recordId,
}),
}),
null,
);
}
}
}
set(commandMenuPageState, newNavigationStackItem.page);
set(commandMenuPageInfoState, {
title: newNavigationStackItem.pageTitle,
Icon: newNavigationStackItem.pageIcon,
instanceId: newNavigationStackItem.pageId,
});
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemsByPageState)
.getValue();
jotaiStore.set(hasUserSelectedCommandState.atom, false);
}, [closeCommandMenu]);
for (const [pageId, morphItems] of currentMorphItems.entries()) {
if (!newNavigationStack.some((item) => item.pageId === pageId)) {
store.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId,
targetObjectId: morphItems[0].recordId,
}),
}),
null,
);
}
}
const navigateCommandMenuHistory = useCallback((pageIndex: number) => {
const currentNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
const newMorphItems = new Map(
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
newNavigationStack.some((item) => item.pageId === pageId),
),
const newNavigationStack = currentNavigationStack.slice(0, pageIndex + 1);
jotaiStore.set(commandMenuNavigationStackState.atom, newNavigationStack);
const newNavigationStackItem = newNavigationStack.at(-1);
if (!isDefined(newNavigationStackItem)) {
throw new Error(
`No command menu navigation stack item found for index ${pageIndex}`,
);
}
jotaiStore.set(commandMenuPageState.atom, newNavigationStackItem.page);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: newNavigationStackItem.pageTitle,
Icon: newNavigationStackItem.pageIcon,
instanceId: newNavigationStackItem.pageId,
});
const currentMorphItems = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
for (const [pageId, morphItems] of currentMorphItems.entries()) {
if (!newNavigationStack.some((item) => item.pageId === pageId)) {
jotaiStore.set(
activeTabIdComponentState.atomFamily({
instanceId: getShowPageTabListComponentId({
pageId,
targetObjectId: morphItems[0].recordId,
}),
}),
null,
);
}
}
set(commandMenuNavigationMorphItemsByPageState, newMorphItems);
const newMorphItems = new Map(
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
newNavigationStack.some((item) => item.pageId === pageId),
),
);
set(hasUserSelectedCommandState, false);
};
},
[store],
);
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
newMorphItems,
);
jotaiStore.set(hasUserSelectedCommandState.atom, false);
}, []);
return {
goBackFromCommandMenu,
@@ -11,10 +11,10 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { useGlobalHotkeys } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeys';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { Key } from 'ts-key-enum';
import { CommandMenuPages } from 'twenty-shared/types';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
@@ -30,15 +30,15 @@ export const useCommandMenuHotKeys = () => {
const { setGlobalCommandMenuContext } = useSetGlobalCommandMenuContext();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuSearch = useRecoilValueV2(commandMenuSearchState);
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
const commandMenuPage = useRecoilValue(commandMenuPageState);
const commandMenuPage = useRecoilValueV2(commandMenuPageState);
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const contextStoreTargetedRecordsRuleComponent = useRecoilComponentValue(
const contextStoreTargetedRecordsRuleComponent = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
COMMAND_MENU_COMPONENT_INSTANCE_ID,
);
@@ -11,15 +11,15 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
import { t } from '@lingui/core/macro';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { AppPath } from 'twenty-shared/types';
import { Avatar } from 'twenty-ui/display';
import { useDebounce } from 'use-debounce';
import { useSearchQuery } from '~/generated/graphql';
export const useCommandMenuSearchRecords = () => {
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const commandMenuSearch = useRecoilValueV2(commandMenuSearchState);
const coreClient = useApolloCoreClient();
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
@@ -1,5 +1,6 @@
import { commandMenuNavigationMorphItemsByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsByPageState';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
type UpdateNavigationMorphItemsByPageParams = {
pageId: string;
@@ -8,26 +9,28 @@ type UpdateNavigationMorphItemsByPageParams = {
};
export const useCommandMenuUpdateNavigationMorphItemsByPage = () => {
const updateCommandMenuNavigationMorphItemsByPage = useRecoilCallback(
({ set, snapshot }) =>
async ({
pageId,
const updateCommandMenuNavigationMorphItemsByPage = useCallback(
async ({
pageId,
objectMetadataId,
objectRecordIds,
}: UpdateNavigationMorphItemsByPageParams) => {
const currentMorphItems = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
const newMorphItems = objectRecordIds.map((recordId) => ({
objectMetadataId,
objectRecordIds,
}: UpdateNavigationMorphItemsByPageParams) => {
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemsByPageState)
.getValue();
recordId,
}));
const newMorphItems = objectRecordIds.map((recordId) => ({
objectMetadataId,
recordId,
}));
const newMorphItemsMap = new Map(currentMorphItems);
newMorphItemsMap.set(pageId, newMorphItems);
set(commandMenuNavigationMorphItemsByPageState, newMorphItemsMap);
},
const newMorphItemsMap = new Map(currentMorphItems);
newMorphItemsMap.set(pageId, newMorphItems);
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
newMorphItemsMap,
);
},
[],
);
@@ -7,153 +7,135 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
export const useCopyContextStoreStates = () => {
const copyContextStoreStates = useRecoilCallback(
({ snapshot, set }) =>
({
instanceIdToCopyFrom,
instanceIdToCopyTo,
}: {
instanceIdToCopyFrom: string;
instanceIdToCopyTo: string;
}) => {
const contextStoreCurrentObjectMetadataItemId = snapshot
.getLoadable(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const copyContextStoreStates = useCallback(
({
instanceIdToCopyFrom,
instanceIdToCopyTo,
}: {
instanceIdToCopyFrom: string;
instanceIdToCopyTo: string;
}) => {
const contextStoreCurrentObjectMetadataItemId = jotaiStore.get(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentObjectMetadataItemId,
);
jotaiStore.set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentObjectMetadataItemId,
);
const contextStoreTargetedRecordsRule = snapshot
.getLoadable(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreTargetedRecordsRule = jotaiStore.get(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreTargetedRecordsRule,
);
jotaiStore.set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreTargetedRecordsRule,
);
const contextStoreNumberOfSelectedRecords = snapshot
.getLoadable(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreNumberOfSelectedRecords = jotaiStore.get(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreNumberOfSelectedRecords,
);
jotaiStore.set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreNumberOfSelectedRecords,
);
const contextStoreFilters = snapshot
.getLoadable(
contextStoreFiltersComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreFilters = jotaiStore.get(
contextStoreFiltersComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreFilters,
);
jotaiStore.set(
contextStoreFiltersComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreFilters,
);
const contextStoreFilterGroups = snapshot
.getLoadable(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreFilterGroups = jotaiStore.get(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreFilterGroups,
);
jotaiStore.set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreFilterGroups,
);
const contextStoreAnyFieldFilterValue = snapshot
.getLoadable(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreAnyFieldFilterValue = jotaiStore.get(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreAnyFieldFilterValue,
);
jotaiStore.set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreAnyFieldFilterValue,
);
const contextStoreCurrentViewId = snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreCurrentViewId = jotaiStore.get(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentViewId,
);
jotaiStore.set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentViewId,
);
const contextStoreCurrentViewType = snapshot
.getLoadable(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreCurrentViewType = jotaiStore.get(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentViewType,
);
jotaiStore.set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreCurrentViewType,
);
const contextStoreIsFullTabWidgetInEditMode = snapshot
.getLoadable(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
)
.getValue();
const contextStoreIsFullTabWidgetInEditMode = jotaiStore.get(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: instanceIdToCopyFrom,
}),
);
set(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreIsFullTabWidgetInEditMode,
);
},
jotaiStore.set(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: instanceIdToCopyTo,
}),
contextStoreIsFullTabWidgetInEditMode,
);
},
[],
);
@@ -9,13 +9,12 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useStore } from 'jotai';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
import { type CommandMenuPages } from 'twenty-shared/types';
import { type IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -36,121 +35,110 @@ export const useNavigateCommandMenu = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const store = useStore();
const openCommandMenu = useCallback(() => {
const isCommandMenuOpened = jotaiStore.get(isCommandMenuOpenedStateV2.atom);
const openCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
() => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
const isCommandMenuClosing = jotaiStore.get(isCommandMenuClosingState.atom);
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
if (isCommandMenuClosing) {
commandMenuCloseAnimationCompleteCleanup();
}
if (isCommandMenuClosing) {
commandMenuCloseAnimationCompleteCleanup();
}
if (isCommandMenuOpened) {
return;
}
if (isCommandMenuOpened) {
return;
}
pushFocusItemToFocusStack({
focusId: SIDE_PANEL_FOCUS_ID,
component: {
type: FocusComponentType.SIDE_PANEL,
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
},
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
});
copyContextStoreStates({
instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
set(isCommandMenuOpenedState, true);
store.set(isCommandMenuOpenedStateV2.atom, true);
set(hasUserSelectedCommandState, false);
pushFocusItemToFocusStack({
focusId: SIDE_PANEL_FOCUS_ID,
component: {
type: FocusComponentType.SIDE_PANEL,
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
},
[
copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup,
pushFocusItemToFocusStack,
store,
],
);
globalHotkeysConfig: {
enableGlobalHotkeysConflictingWithKeyboard: false,
},
});
const navigateCommandMenu = useRecoilCallback(
({ snapshot, set }) => {
return ({
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
focusTitleInput = false,
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
focusTitleInput?: boolean;
}) => {
const computedPageId = pageId || v4();
copyContextStoreStates({
instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
openCommandMenu();
set(commandMenuPageState, page);
set(commandMenuPageInfoState, {
title: pageTitle,
Icon: pageIcon,
instanceId: computedPageId,
});
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(hasUserSelectedCommandState.atom, false);
}, [
copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup,
pushFocusItemToFocusStack,
]);
if (focusTitleInput) {
set(
commandMenuShouldFocusTitleInputComponentState.atomFamily({
instanceId: computedPageId,
}),
true,
);
}
const navigateCommandMenu = useCallback(
({
page,
pageTitle,
pageIcon,
pageIconColor,
pageId,
focusTitleInput = false,
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
focusTitleInput?: boolean;
}) => {
const computedPageId = pageId || v4();
const isCommandMenuClosing = snapshot
.getLoadable(isCommandMenuClosingState)
.getValue();
openCommandMenu();
jotaiStore.set(commandMenuPageState.atom, page);
jotaiStore.set(commandMenuPageInfoState.atom, {
title: pageTitle,
Icon: pageIcon,
instanceId: computedPageId,
});
const currentNavigationStack = isCommandMenuClosing
? []
: snapshot.getLoadable(commandMenuNavigationStackState).getValue();
if (focusTitleInput) {
jotaiStore.set(
commandMenuShouldFocusTitleInputComponentState.atomFamily({
instanceId: computedPageId,
}),
true,
);
}
if (resetNavigationStack) {
set(commandMenuNavigationStackState, [
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
const isCommandMenuClosing = jotaiStore.get(
isCommandMenuClosingState.atom,
);
set(commandMenuNavigationMorphItemsByPageState, new Map());
} else {
set(commandMenuNavigationStackState, [
...currentNavigationStack,
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
}
};
const currentNavigationStack = isCommandMenuClosing
? []
: jotaiStore.get(commandMenuNavigationStackState.atom);
if (resetNavigationStack) {
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
new Map(),
);
} else {
jotaiStore.set(commandMenuNavigationStackState.atom, [
...currentNavigationStack,
{
page,
pageTitle,
pageIcon,
pageIconColor,
pageId: computedPageId,
},
]);
}
},
[openCommandMenu],
);
@@ -1,7 +1,8 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconCalendarEvent } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -9,50 +10,48 @@ import { v4 } from 'uuid';
export const useOpenCalendarEventInCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openCalendarEventInCommandMenu = useRecoilCallback(
({ set }) => {
return (calendarEventId: string) => {
const pageComponentInstanceId = v4();
const openCalendarEventInCommandMenu = useCallback(
(calendarEventId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
calendarEventId,
);
jotaiStore.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,
// );
// 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,
// },
// ],
// ]),
// );
// 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({
page: CommandMenuPages.ViewCalendarEvent,
pageTitle: t`Calendar Event`,
pageIcon: IconCalendarEvent,
pageId: pageComponentInstanceId,
});
},
[navigateCommandMenu],
);
@@ -1,7 +1,8 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconMail } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -9,50 +10,48 @@ import { v4 } from 'uuid';
export const useOpenEmailThreadInCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openEmailThreadInCommandMenu = useRecoilCallback(
({ set }) => {
return (emailThreadId: string) => {
const pageComponentInstanceId = v4();
const openEmailThreadInCommandMenu = useCallback(
(emailThreadId: string) => {
const pageComponentInstanceId = v4();
set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
emailThreadId,
);
jotaiStore.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,
// );
// 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,
// },
// ],
// ]),
// );
// set(
// commandMenuNavigationMorphItemsState,
// new Map([
// ...snapshot
// .getLoadable(commandMenuNavigationMorphItemsState)
// .getValue(),
// [
// pageComponentInstanceId,
// {
// objectMetadataId: objectMetadataItem?.id,
// recordId: emailThreadId,
// },
// ],
// ]),
// );
navigateCommandMenu({
page: CommandMenuPages.ViewEmailThread,
pageTitle: t`Email Thread`,
pageIcon: IconMail,
pageId: pageComponentInstanceId,
});
};
navigateCommandMenu({
page: CommandMenuPages.ViewEmailThread,
pageTitle: t`Email Thread`,
pageIcon: IconMail,
pageId: pageComponentInstanceId,
});
},
[navigateCommandMenu],
);
@@ -1,6 +1,6 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableFrontComponentIdComponentState } from '@/command-menu/pages/front-component/states/viewableFrontComponentIdComponentState';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { type IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -8,38 +8,34 @@ import { v4 } from 'uuid';
export const useOpenFrontComponentInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const openFrontComponentInCommandMenu = useRecoilCallback(
({ set }) =>
({
frontComponentId,
pageTitle,
pageIcon,
resetNavigationStack = false,
}: {
frontComponentId: string;
pageTitle: string;
pageIcon: IconComponent;
resetNavigationStack?: boolean;
}) => {
const pageComponentInstanceId = v4();
const openFrontComponentInCommandMenu = ({
frontComponentId,
pageTitle,
pageIcon,
resetNavigationStack = false,
}: {
frontComponentId: string;
pageTitle: string;
pageIcon: IconComponent;
resetNavigationStack?: boolean;
}) => {
const pageComponentInstanceId = v4();
set(
viewableFrontComponentIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
frontComponentId,
);
jotaiStore.set(
viewableFrontComponentIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
frontComponentId,
);
navigateCommandMenu({
page: CommandMenuPages.ViewFrontComponent,
pageTitle,
pageIcon,
pageId: pageComponentInstanceId,
resetNavigationStack,
});
},
[navigateCommandMenu],
);
navigateCommandMenu({
page: CommandMenuPages.ViewFrontComponent,
pageTitle,
pageIcon,
pageId: pageComponentInstanceId,
resetNavigationStack,
});
};
return {
openFrontComponentInCommandMenu,
@@ -6,8 +6,9 @@ import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRec
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { msg, t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { IconArrowMerge } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -39,43 +40,38 @@ export const useOpenMergeRecordsPageInCommandMenu = ({
},
});
const openMergeRecordsPageInCommandMenu = useRecoilCallback(
({ set }) => {
return async () => {
const pageId = v4();
const openMergeRecordsPageInCommandMenu = useCallback(async () => {
const pageId = v4();
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageId,
}),
objectMetadataItem.id,
);
await updateCommandMenuNavigationMorphItemsByPage({
pageId,
objectMetadataId: objectMetadataItem.id,
objectRecordIds,
});
const { records } = await findManyRecordsLazy();
upsertRecordsInStore({ partialRecords: records ?? [] });
navigateCommandMenu({
page: CommandMenuPages.MergeRecords,
pageTitle: t(msg`Merge records`),
pageIcon: IconArrowMerge,
pageId,
});
};
},
[
jotaiStore.set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageId,
}),
objectMetadataItem.id,
);
await updateCommandMenuNavigationMorphItemsByPage({
pageId,
objectMetadataId: objectMetadataItem.id,
objectRecordIds,
findManyRecordsLazy,
upsertRecordsInStore,
navigateCommandMenu,
updateCommandMenuNavigationMorphItemsByPage,
],
);
});
const { records } = await findManyRecordsLazy();
upsertRecordsInStore({ partialRecords: records ?? [] });
navigateCommandMenu({
page: CommandMenuPages.MergeRecords,
pageTitle: t(msg`Merge records`),
pageIcon: IconArrowMerge,
pageId,
});
}, [
objectMetadataItem.id,
objectRecordIds,
findManyRecordsLazy,
upsertRecordsInStore,
navigateCommandMenu,
updateCommandMenuNavigationMorphItemsByPage,
]);
return {
openMergeRecordsPageInCommandMenu,
@@ -15,14 +15,13 @@ import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objec
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { useRunWorkflowRunOpeningInCommandMenuSideEffects } from '@/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { v4 } from 'uuid';
@@ -35,168 +34,163 @@ export const useOpenRecordInCommandMenu = () => {
const { runWorkflowRunOpeningInCommandMenuSideEffects } =
useRunWorkflowRunOpeningInCommandMenuSideEffects();
const openRecordInCommandMenu = useRecoilCallback(
({ set, snapshot }) => {
return ({
recordId,
objectNameSingular,
isNewRecord = false,
resetNavigationStack = false,
}: {
recordId: string;
objectNameSingular: string;
isNewRecord?: boolean;
resetNavigationStack?: boolean;
}) => {
const navigationStack = getSnapshotValue(
snapshot,
commandMenuNavigationStackState,
);
const openRecordInCommandMenu = useCallback(
({
recordId,
objectNameSingular,
isNewRecord = false,
resetNavigationStack = false,
}: {
recordId: string;
objectNameSingular: string;
isNewRecord?: boolean;
resetNavigationStack?: boolean;
}) => {
const navigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
const currentNavigationStackItem = navigationStack.at(-1);
const currentNavigationStackItem = navigationStack.at(-1);
if (isDefined(currentNavigationStackItem)) {
const currentRecordId = getSnapshotValue(
snapshot,
viewableRecordIdComponentState.atomFamily({
instanceId: currentNavigationStackItem.pageId,
}),
);
if (currentRecordId === recordId) {
return;
}
}
const pageComponentInstanceId = v4();
set(
viewableRecordNameSingularComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectNameSingular,
);
set(
if (isDefined(currentNavigationStackItem)) {
const currentRecordId = jotaiStore.get(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
recordId,
);
set(viewableRecordIdState, recordId);
const objectMetadataItem = jotaiStore.get(
objectMetadataItemFamilySelector.selectorFamily({
objectName: objectNameSingular,
objectNameType: 'singular',
instanceId: currentNavigationStackItem.pageId,
}),
);
if (!objectMetadataItem) {
throw new Error(
`No object metadata item found for object name ${objectNameSingular}`,
);
if (currentRecordId === recordId) {
return;
}
}
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectMetadataItem.id,
const pageComponentInstanceId = v4();
jotaiStore.set(
viewableRecordNameSingularComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectNameSingular,
);
jotaiStore.set(
viewableRecordIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
recordId,
);
jotaiStore.set(viewableRecordIdState.atom, recordId);
const objectMetadataItem = jotaiStore.get(
objectMetadataItemFamilySelector.selectorFamily({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
);
if (!objectMetadataItem) {
throw new Error(
`No object metadata item found for object name ${objectNameSingular}`,
);
}
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
{
mode: 'selection',
selectedRecordIds: [recordId],
},
);
jotaiStore.set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
objectMetadataItem.id,
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
1,
);
jotaiStore.set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
{
mode: 'selection',
selectedRecordIds: [recordId],
},
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
ContextStoreViewType.ShowPage,
);
jotaiStore.set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
1,
);
set(
jotaiStore.set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
ContextStoreViewType.ShowPage,
);
jotaiStore.set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
jotaiStore.get(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
snapshot
.getLoadable(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
)
.getValue(),
);
),
);
set(
jotaiStore.set(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
jotaiStore.get(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: pageComponentInstanceId,
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
snapshot
.getLoadable(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}),
)
.getValue(),
);
),
);
const currentMorphItems = snapshot
.getLoadable(commandMenuNavigationMorphItemsByPageState)
.getValue();
const currentMorphItems = jotaiStore.get(
commandMenuNavigationMorphItemsByPageState.atom,
);
const morphItemToAdd = {
objectMetadataId: objectMetadataItem.id,
recordId,
};
const newMorphItemsMap = new Map(currentMorphItems);
newMorphItemsMap.set(pageComponentInstanceId, [morphItemToAdd]);
set(commandMenuNavigationMorphItemsByPageState, newMorphItemsMap);
const Icon = objectMetadataItem?.icon
? getIcon(objectMetadataItem.icon)
: getIcon('IconList');
const IconColor = getIconColorForObjectType({
objectType: objectMetadataItem.nameSingular,
theme,
});
const objectLabelSingular = objectMetadataItem.labelSingular;
navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: isNewRecord
? t`New ${objectLabelSingular}`
: objectLabelSingular,
pageIcon: Icon,
pageIconColor: IconColor,
pageId: pageComponentInstanceId,
resetNavigationStack,
});
if (objectNameSingular === CoreObjectNameSingular.WorkflowRun) {
runWorkflowRunOpeningInCommandMenuSideEffects({
objectMetadataItem,
recordId,
});
}
const morphItemToAdd = {
objectMetadataId: objectMetadataItem.id,
recordId,
};
const newMorphItemsMap = new Map(currentMorphItems);
newMorphItemsMap.set(pageComponentInstanceId, [morphItemToAdd]);
jotaiStore.set(
commandMenuNavigationMorphItemsByPageState.atom,
newMorphItemsMap,
);
const Icon = objectMetadataItem?.icon
? getIcon(objectMetadataItem.icon)
: getIcon('IconList');
const IconColor = getIconColorForObjectType({
objectType: objectMetadataItem.nameSingular,
theme,
});
const objectLabelSingular = objectMetadataItem.labelSingular;
navigateCommandMenu({
page: CommandMenuPages.ViewRecord,
pageTitle: isNewRecord
? t`New ${objectLabelSingular}`
: objectLabelSingular,
pageIcon: Icon,
pageIconColor: IconColor,
pageId: pageComponentInstanceId,
resetNavigationStack,
});
if (objectNameSingular === CoreObjectNameSingular.WorkflowRun) {
runWorkflowRunOpeningInCommandMenuSideEffects({
objectMetadataItem,
recordId,
});
}
},
[
getIcon,
@@ -2,7 +2,7 @@ import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandM
import { CommandMenuPages } from 'twenty-shared/types';
import { msg, t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { IconBoxMultiple } from 'twenty-ui/display';
type UseOpenUpdateMultipleRecordsPageInCommandMenuProps = {
@@ -14,15 +14,13 @@ export const useOpenUpdateMultipleRecordsPageInCommandMenu = ({
}: UseOpenUpdateMultipleRecordsPageInCommandMenuProps) => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const openUpdateMultipleRecordsPageInCommandMenu = useRecoilCallback(() => {
return async () => {
navigateCommandMenu({
page: CommandMenuPages.UpdateRecords,
pageTitle: t(msg`Update records`),
pageIcon: IconBoxMultiple,
pageId: contextStoreInstanceId,
});
};
const openUpdateMultipleRecordsPageInCommandMenu = useCallback(async () => {
navigateCommandMenu({
page: CommandMenuPages.UpdateRecords,
pageTitle: t(msg`Update records`),
pageIcon: IconBoxMultiple,
pageId: contextStoreInstanceId,
});
}, [navigateCommandMenu, contextStoreInstanceId]);
return {
@@ -5,63 +5,62 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c
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';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
export const useResetContextStoreStates = () => {
const resetContextStoreStates = useRecoilCallback(({ set }) => {
return (instanceId: string) => {
set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId,
}),
undefined,
);
const resetContextStoreStates = useCallback((instanceId: string) => {
jotaiStore.set(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId,
}),
undefined,
);
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId,
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
jotaiStore.set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId,
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId,
}),
0,
);
jotaiStore.set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId,
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId,
}),
[],
);
jotaiStore.set(
contextStoreFiltersComponentState.atomFamily({
instanceId,
}),
[],
);
set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId,
}),
[],
);
jotaiStore.set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId,
}),
[],
);
set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId,
}),
'',
);
jotaiStore.set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId,
}),
'',
);
set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId,
}),
undefined,
);
};
jotaiStore.set(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId,
}),
undefined,
);
}, []);
return { resetContextStoreStates };
@@ -10,75 +10,71 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
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';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
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,
});
const setGlobalCommandMenuContext = useCallback(() => {
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: [],
},
);
jotaiStore.set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
0,
);
jotaiStore.set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
0,
);
set(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
jotaiStore.set(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
jotaiStore.set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
'',
);
jotaiStore.set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
'',
);
set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
jotaiStore.set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
instanceId: '',
});
jotaiStore.set(commandMenuPageInfoState.atom, {
title: undefined,
Icon: undefined,
instanceId: '',
});
set(hasUserSelectedCommandState, false);
};
},
[copyContextStoreStates],
);
jotaiStore.set(hasUserSelectedCommandState.atom, false);
}, [copyContextStoreStates]);
return {
setGlobalCommandMenuContext,
@@ -1,53 +1,54 @@
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useCallback } from 'react';
import { type IconComponent, IconDotsVertical } from 'twenty-ui/display';
export const useUpdateCommandMenuPageInfo = () => {
const updateCommandMenuPageInfo = useRecoilCallback(
({ snapshot, set }) =>
({
pageTitle,
pageIcon,
}: {
pageTitle?: string;
pageIcon?: IconComponent;
}) => {
const commandMenuPageInfo = snapshot
.getLoadable(commandMenuPageInfoState)
.getValue();
const updateCommandMenuPageInfo = useCallback(
({
pageTitle,
pageIcon,
}: {
pageTitle?: string;
pageIcon?: IconComponent;
}) => {
const commandMenuPageInfo = jotaiStore.get(commandMenuPageInfoState.atom);
const newCommandMenuPageInfo = {
...commandMenuPageInfo,
title: pageTitle ?? commandMenuPageInfo.title ?? '',
Icon: pageIcon ?? commandMenuPageInfo.Icon ?? IconDotsVertical,
};
const newCommandMenuPageInfo = {
...commandMenuPageInfo,
title: pageTitle ?? commandMenuPageInfo.title ?? '',
Icon: pageIcon ?? commandMenuPageInfo.Icon ?? IconDotsVertical,
};
set(commandMenuPageInfoState, newCommandMenuPageInfo);
jotaiStore.set(commandMenuPageInfoState.atom, newCommandMenuPageInfo);
const commandMenuNavigationStack = snapshot
.getLoadable(commandMenuNavigationStackState)
.getValue();
const commandMenuNavigationStack = jotaiStore.get(
commandMenuNavigationStackState.atom,
);
const lastCommandMenuNavigationStackItem =
commandMenuNavigationStack.at(-1);
const lastCommandMenuNavigationStackItem =
commandMenuNavigationStack.at(-1);
if (!lastCommandMenuNavigationStackItem) {
return;
}
if (!lastCommandMenuNavigationStackItem) {
return;
}
const newCommandMenuNavigationStack = [
...commandMenuNavigationStack.slice(0, -1),
{
page: lastCommandMenuNavigationStackItem.page,
pageTitle: newCommandMenuPageInfo.title,
pageIcon: newCommandMenuPageInfo.Icon,
pageId: lastCommandMenuNavigationStackItem.pageId,
},
];
const newCommandMenuNavigationStack = [
...commandMenuNavigationStack.slice(0, -1),
{
page: lastCommandMenuNavigationStackItem.page,
pageTitle: newCommandMenuPageInfo.title,
pageIcon: newCommandMenuPageInfo.Icon,
pageId: lastCommandMenuNavigationStackItem.pageId,
},
];
set(commandMenuNavigationStackState, newCommandMenuNavigationStack);
},
jotaiStore.set(
commandMenuNavigationStackState.atom,
newCommandMenuNavigationStack,
);
},
[],
);
@@ -3,11 +3,12 @@ import { commandMenuWorkflowIdComponentState } from '@/command-menu/pages/workfl
import { commandMenuWorkflowRunIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowRunIdComponentState';
import { commandMenuWorkflowStepIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowStepIdComponentState';
import { commandMenuWorkflowVersionIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowVersionIdComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { type WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { useSetInitialWorkflowRunRightDrawerTab } from '@/workflow/workflow-diagram/hooks/useSetInitialWorkflowRunRightDrawerTab';
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { t } from '@lingui/core/macro';
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -22,233 +23,221 @@ export const useWorkflowCommandMenu = () => {
const { setInitialWorkflowRunRightDrawerTab } =
useSetInitialWorkflowRunRightDrawerTab();
const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
const openWorkflowTriggerTypeInCommandMenu = useCallback(
(workflowId: string) => {
const pageId = v4();
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowTriggerSelectType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowCreateStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepCreate,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowEditStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (
workflowId: string,
title: string,
icon: IconComponent,
stepId?: string,
) => {
const pageId = v4();
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
if (isDefined(stepId)) {
set(
commandMenuWorkflowStepIdComponentState.atomFamily({
instanceId: pageId,
}),
stepId,
);
set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowId,
}),
stepId,
);
}
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowEditStepTypeInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEditType,
pageTitle: t`Select action`,
pageIcon: IconSettingsAutomation,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return ({
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
workflowVersionId,
title,
icon,
stepId,
}: {
workflowId: string;
workflowVersionId: string;
title: string;
icon: IconComponent;
stepId?: string;
}) => {
const pageId = v4();
);
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
set(
commandMenuWorkflowVersionIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowVersionId,
);
if (isDefined(stepId)) {
set(
commandMenuWorkflowStepIdComponentState.atomFamily({
instanceId: pageId,
}),
stepId,
);
set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowVersionId,
}),
stepId,
);
}
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
navigateCommandMenu({
page: CommandMenuPages.WorkflowTriggerSelectType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId,
});
},
[navigateCommandMenu],
);
const openWorkflowRunViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return ({
workflowId,
workflowRunId,
title,
icon,
workflowSelectedNode,
stepExecutionStatus,
}: {
workflowId: string;
workflowRunId: string;
title: string;
icon: IconComponent;
workflowSelectedNode: string;
stepExecutionStatus: WorkflowRunStepStatus;
}) => {
const pageId = v4();
const openWorkflowCreateStepInCommandMenu = useCallback(
(workflowId: string) => {
const pageId = v4();
set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
set(
commandMenuWorkflowRunIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowRunId,
);
set(
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepCreate,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId,
});
},
[navigateCommandMenu],
);
const openWorkflowEditStepInCommandMenu = useCallback(
(
workflowId: string,
title: string,
icon: IconComponent,
stepId?: string,
) => {
const pageId = v4();
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
if (isDefined(stepId)) {
jotaiStore.set(
commandMenuWorkflowStepIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowSelectedNode,
stepId,
);
set(
jotaiStore.set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowRunId,
instanceId: workflowId,
}),
workflowSelectedNode,
stepId,
);
}
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: title,
pageIcon: icon,
pageId,
});
},
[navigateCommandMenu],
);
const openWorkflowEditStepTypeInCommandMenu = useCallback(
(workflowId: string) => {
const pageId = v4();
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEditType,
pageTitle: t`Select action`,
pageIcon: IconSettingsAutomation,
pageId,
});
},
[navigateCommandMenu],
);
const openWorkflowViewStepInCommandMenu = useCallback(
({
workflowId,
workflowVersionId,
title,
icon,
stepId,
}: {
workflowId: string;
workflowVersionId: string;
title: string;
icon: IconComponent;
stepId?: string;
}) => {
const pageId = v4();
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
jotaiStore.set(
commandMenuWorkflowVersionIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowVersionId,
);
if (isDefined(stepId)) {
jotaiStore.set(
commandMenuWorkflowStepIdComponentState.atomFamily({
instanceId: pageId,
}),
stepId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
jotaiStore.set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowVersionId,
}),
stepId,
);
}
setInitialWorkflowRunRightDrawerTab({
workflowSelectedNode,
stepExecutionStatus,
});
};
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
},
[navigateCommandMenu],
);
const openWorkflowRunViewStepInCommandMenu = useCallback(
({
workflowId,
workflowRunId,
title,
icon,
workflowSelectedNode,
stepExecutionStatus,
}: {
workflowId: string;
workflowRunId: string;
title: string;
icon: IconComponent;
workflowSelectedNode: string;
stepExecutionStatus: WorkflowRunStepStatus;
}) => {
const pageId = v4();
jotaiStore.set(
commandMenuWorkflowIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowId,
);
jotaiStore.set(
commandMenuWorkflowRunIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowRunId,
);
jotaiStore.set(
commandMenuWorkflowStepIdComponentState.atomFamily({
instanceId: pageId,
}),
workflowSelectedNode,
);
jotaiStore.set(
workflowSelectedNodeComponentState.atomFamily({
instanceId: workflowRunId,
}),
workflowSelectedNode,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
setInitialWorkflowRunRightDrawerTab({
workflowSelectedNode,
stepExecutionStatus,
});
},
[navigateCommandMenu, setInitialWorkflowRunRightDrawerTab],
);