Skip to content

Commit

Permalink
410 open in side panel (#10363)
Browse files Browse the repository at this point in the history
Closes twentyhq/core-team-issues#410

- Added `openRecordIn` column in the `view` entity, which is set to
`SIDE_PANEL` by default
- Created a new option inside the view option dropdown to be able to set
`openRecordIn`
- Updated all record show page openings to reflect the setting behavior
- For `workflow`, `workflowVersion` and `workflowRun` (what I call
workflow objects), we want the default view `openRecordIn` to be set to
`RECORD_PAGE`. When seeding the views for the new workspaces, we set
`openRecordIn` to `RECORD_PAGE` for workflow objects. Since the workflow
objects views `openRecordIn` will be set to the default value
`SIDE_PANEL` for the existing workspaces when the sync metadata runs, I
created a script to run in the 0.43 update to update this value.
- Updated `closeCommandMenu` because of problems introduced by the
animate presence wrapper around the command menu. We now reset the
states at the end of the animation.

Note: We want to be able to open all workflow objects pages in the side
panel, but this requires some refactoring of the workflow module. For
now @Bonapara wanted to allow the possibility to change the
`openRecordIn` setting to `SIDE_PANEL` even for the workflows even if
it's buggy and not ready for the moment. Since this is an experimental
feature, it shouldn't cause too many problems.
  • Loading branch information
bosiraphael authored Feb 21, 2025
1 parent e301c78 commit 9f454c5
Show file tree
Hide file tree
Showing 102 changed files with 928 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ export const CommandMenuContainer = ({
}: {
children: React.ReactNode;
}) => {
const { toggleCommandMenu, closeCommandMenu } = useCommandMenu();
const {
toggleCommandMenu,
closeCommandMenu,
onCommandMenuCloseAnimationComplete,
} = useCommandMenu();

const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);

Expand Down Expand Up @@ -98,7 +102,7 @@ export const CommandMenuContainer = ({
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
isInRightDrawer: true,
onActionExecutedCallback: ({ key }) => {
if (
key !== RecordAgnosticActionsKey.SEARCH_RECORDS &&
Expand All @@ -121,7 +125,10 @@ export const CommandMenuContainer = ({
<RunWorkflowRecordAgnosticActionMenuEntriesSetter />
)}
<ActionMenuConfirmationModals />
<AnimatePresence mode="wait">
<AnimatePresence
mode="wait"
onExitComplete={onCommandMenuCloseAnimationComplete}
>
{isCommandMenuOpened && (
<StyledCommandMenu
data-testid="command-menu"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ export const CommandMenuContextChipGroupsWithRecordSelection = ({
/>
));

const recordSelectionContextChip = totalCount
? {
text: getSelectedRecordsContextText(
objectMetadataItem,
records,
totalCount,
),
Icons: Avatars,
}
: undefined;
const recordSelectionContextChip =
totalCount && records.length > 0
? {
text: getSelectedRecordsContextText(
objectMetadataItem,
records,
totalCount,
),
Icons: Avatars,
}
: undefined;

const contextChipsWithRecordSelection = [
recordSelectionContextChip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const CommandMenuContextRecordChip = ({
instanceId,
});

if (loading || !totalCount) {
if (loading || !totalCount || records.length === 0) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ describe('useCommandMenu', () => {

act(() => {
result.current.commandMenu.goBackFromCommandMenu();
result.current.commandMenu.onCommandMenuCloseAnimationComplete();
});

expect(result.current.commandMenuNavigationStack).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,33 @@ export const useCommandMenu = () => {
);

const closeCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
({ set }) =>
() => {
const isCommandMenuOpened = snapshot
.getLoadable(isCommandMenuOpenedState)
.getValue();
set(isCommandMenuOpenedState, false);
},
[],
);

if (isCommandMenuOpened) {
resetContextStoreStates('command-menu');
resetContextStoreStates('command-menu-previous');

set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
});
set(isCommandMenuOpenedState, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationStackState, []);
resetSelectedItem();
set(hasUserSelectedCommandState, false);
goBackToPreviousHotkeyScope();

emitRightDrawerCloseEvent();
}
const onCommandMenuCloseAnimationComplete = useRecoilCallback(
({ set }) =>
() => {
resetContextStoreStates('command-menu');
resetContextStoreStates('command-menu-previous');

set(viewableRecordIdState, null);
set(commandMenuPageState, CommandMenuPages.Root);
set(commandMenuPageInfoState, {
title: undefined,
Icon: undefined,
});
set(isCommandMenuOpenedState, false);
set(commandMenuSearchState, '');
set(commandMenuNavigationStackState, []);
resetSelectedItem();
set(hasUserSelectedCommandState, false);
goBackToPreviousHotkeyScope();

emitRightDrawerCloseEvent();
},
[goBackToPreviousHotkeyScope, resetContextStoreStates, resetSelectedItem],
);
Expand All @@ -109,7 +111,10 @@ export const useCommandMenu = () => {
page,
pageTitle,
pageIcon,
}: CommandMenuNavigationStackItem) => {
resetNavigationStack = false,
}: CommandMenuNavigationStackItem & {
resetNavigationStack?: boolean;
}) => {
set(commandMenuPageState, page);
set(commandMenuPageInfoState, {
title: pageTitle,
Expand All @@ -120,10 +125,14 @@ export const useCommandMenu = () => {
.getLoadable(commandMenuNavigationStackState)
.getValue();

set(commandMenuNavigationStackState, [
...currentNavigationStack,
{ page, pageTitle, pageIcon },
]);
if (resetNavigationStack) {
set(commandMenuNavigationStackState, [{ page, pageTitle, pageIcon }]);
} else {
set(commandMenuNavigationStackState, [
...currentNavigationStack,
{ page, pageTitle, pageIcon },
]);
}
openCommandMenu();
};
},
Expand Down Expand Up @@ -248,6 +257,7 @@ export const useCommandMenu = () => {
? t`New ${capitalizedObjectNameSingular}`
: capitalizedObjectNameSingular,
pageIcon: Icon,
resetNavigationStack: true,
});
};
},
Expand Down Expand Up @@ -315,6 +325,7 @@ export const useCommandMenu = () => {
return {
openRootCommandMenu,
closeCommandMenu,
onCommandMenuCloseAnimationComplete,
navigateCommandMenu,
navigateCommandMenuHistory,
goBackFromCommandMenu,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { AppPath } from '@/types/AppPath';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType';
import { getCompanyObjectMetadataItem } from '~/testing/mock-data/companies';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
Expand Down Expand Up @@ -38,6 +39,7 @@ const renderHooks = ({
type: ViewType.Table,
key: null,
isCompact: false,
openRecordIn: ViewOpenRecordInType.SIDE_PANEL,
viewFields: [],
viewGroups: [],
viewSorts: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { AvatarChip, AvatarChipVariant } from 'twenty-ui';

import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { MouseEvent } from 'react';
import { useRecoilValue } from 'recoil';

export type RecordChipProps = {
objectNameSingular: string;
Expand All @@ -23,8 +27,18 @@ export const RecordChip = ({
record,
});

const { openRecordInCommandMenu } = useCommandMenu();

const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);

const handleClick = (e: MouseEvent<Element>) => {
e.stopPropagation();
if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
openRecordInCommandMenu({
recordId: record.id,
objectNameSingular,
});
}
};

return (
Expand All @@ -36,7 +50,11 @@ export const RecordChip = ({
className={className}
variant={variant}
onClick={handleClick}
to={getLinkToShowPage(objectNameSingular, record)}
to={
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
? getLinkToShowPage(objectNameSingular, record)
: undefined
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ObjectOptionsDropdownRecordGroupFieldsContent } from '@/object-record/o
import { ObjectOptionsDropdownRecordGroupsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent';
import { ObjectOptionsDropdownRecordGroupSortContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupSortContent';
import { ObjectOptionsDropdownViewSettingsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownViewSettingsContent';
import { ObjectOptionsDropdownViewSettingsOpenInContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownViewSettingsOpenInContent';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';

export const ObjectOptionsDropdownContent = () => {
Expand All @@ -14,6 +15,8 @@ export const ObjectOptionsDropdownContent = () => {
switch (currentContentId) {
case 'viewSettings':
return <ObjectOptionsDropdownViewSettingsContent />;
case 'viewSettingsOpenIn':
return <ObjectOptionsDropdownViewSettingsOpenInContent />;
case 'fields':
return <ObjectOptionsDropdownFieldsContent />;
case 'hiddenFields':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewType } from '@/views/types/ViewType';
import { isDefined } from 'twenty-shared';

export const ObjectOptionsDropdownMenuContent = () => {
Expand Down Expand Up @@ -104,20 +103,17 @@ export const ObjectOptionsDropdownMenuContent = () => {
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
{currentView?.name}
</DropdownMenuHeader>
{/** TODO: Should be removed when view settings contains more options */}
{viewType === ViewType.Kanban && (
<>
<DropdownMenuItemsContainer scrollable={false}>
<MenuItem
onClick={() => onContentChange('viewSettings')}
LeftIcon={IconLayout}
text="View settings"
hasSubMenu
/>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
</>
)}

<DropdownMenuItemsContainer scrollable={false}>
<MenuItem
onClick={() => onContentChange('viewSettings')}
LeftIcon={IconLayout}
text="View settings"
hasSubMenu
/>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />

<DropdownMenuItemsContainer scrollable={false}>
<MenuItem
onClick={() => onContentChange('fields')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import {
IconBaselineDensitySmall,
IconChevronLeft,
IconLayoutNavbar,
IconLayoutSidebarRight,
MenuItem,
MenuItemToggle,
} from 'twenty-ui';

import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType';
import { useRecoilValue } from 'recoil';

export const ObjectOptionsDropdownViewSettingsContent = () => {
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();

const { recordIndexId, objectMetadataItem, viewType, resetContent } =
useOptionsDropdown();
const {
recordIndexId,
objectMetadataItem,
viewType,
resetContent,
onContentChange,
} = useOptionsDropdown();

const { isCompactModeActive, setAndPersistIsCompactModeActive } =
useObjectOptionsForBoard({
Expand All @@ -24,12 +35,29 @@ export const ObjectOptionsDropdownViewSettingsContent = () => {
viewBarId: recordIndexId,
});

const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);

return (
<>
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
View settings
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => onContentChange('viewSettingsOpenIn')}
LeftIcon={
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? IconLayoutSidebarRight
: IconLayoutNavbar
}
text="Open in"
contextualText={
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? 'Side Panel'
: 'Record Page'
}
hasSubMenu
/>
{viewType === ViewType.Kanban && (
<MenuItemToggle
LeftIcon={IconBaselineDensitySmall}
Expand Down
Loading

0 comments on commit 9f454c5

Please sign in to comment.