Skip to content

Commit

Permalink
Refactor action menu (#7586)
Browse files Browse the repository at this point in the history
Introduces effects to set the actionMenuEntries
  • Loading branch information
bosiraphael authored Oct 11, 2024
1 parent 9b9b34f commit 3761fbf
Show file tree
Hide file tree
Showing 26 changed files with 447 additions and 319 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useCallback, useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { IconTrash } from 'twenty-ui';

export const DeleteRecordsActionEffect = ({
position,
}: {
position: number;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();

const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
);

const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
);

const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});

const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
useState(false);

const { deleteTableData } = useDeleteTableData({
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
recordIndexId: objectMetadataItem?.namePlural ?? '',
});

const handleDeleteClick = useCallback(() => {
deleteTableData(contextStoreTargetedRecordIds);
}, [deleteTableData, contextStoreTargetedRecordIds]);

const isRemoteObject = objectMetadataItem?.isRemote ?? false;

const numberOfSelectedRecords = contextStoreTargetedRecordIds.length;

const canDelete =
!isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT;

useEffect(() => {
if (canDelete) {
addActionMenuEntry({
key: 'delete',
label: 'Delete',
position,
Icon: IconTrash,
accent: 'danger',
onClick: () => {
setIsDeleteRecordsModalOpen(true);
},
ConfirmationModal: (
<ConfirmationModal
isOpen={isDeleteRecordsModalOpen}
setIsOpen={setIsDeleteRecordsModalOpen}
title={`Delete ${numberOfSelectedRecords} ${
numberOfSelectedRecords === 1 ? `record` : 'records'
}`}
subtitle={`Are you sure you want to delete ${
numberOfSelectedRecords === 1 ? 'this record' : 'these records'
}? ${
numberOfSelectedRecords === 1 ? 'It' : 'They'
} can be recovered from the Options menu.`}
onConfirmClick={() => handleDeleteClick()}
deleteButtonText={`Delete ${
numberOfSelectedRecords > 1 ? 'Records' : 'Record'
}`}
/>
),
});
} else {
removeActionMenuEntry('delete');
}
}, [
canDelete,
addActionMenuEntry,
removeActionMenuEntry,
isDeleteRecordsModalOpen,
numberOfSelectedRecords,
handleDeleteClick,
position,
]);

return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import {
displayedExportProgress,
useExportTableData,
} from '@/object-record/record-index/options/hooks/useExportTableData';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { IconFileExport } from 'twenty-ui';

export const ExportRecordsActionEffect = ({
position,
}: {
position: number;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();

const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
);

const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});

const baseTableDataParams = {
delayMs: 100,
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
recordIndexId: objectMetadataItem?.namePlural ?? '',
};

const { progress, download } = useExportTableData({
...baseTableDataParams,
filename: `${objectMetadataItem?.nameSingular}.csv`,
});

useEffect(() => {
addActionMenuEntry({
key: 'export',
position,
label: displayedExportProgress(progress),
Icon: IconFileExport,
accent: 'default',
onClick: () => download(),
});

return () => {
removeActionMenuEntry('export');
};
}, [download, progress, addActionMenuEntry, removeActionMenuEntry, position]);
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';

export const ManageFavoritesActionEffect = ({
position,
}: {
position: number;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();

const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
);
const contextStoreCurrentObjectMetadataId = useRecoilValue(
contextStoreCurrentObjectMetadataIdState,
);

const { favorites, createFavorite, deleteFavorite } = useFavorites();

const selectedRecordId = contextStoreTargetedRecordIds[0];

const selectedRecord = useRecoilValue(
recordStoreFamilyState(selectedRecordId),
);

const { objectMetadataItem } = useObjectMetadataItemById({
objectId: contextStoreCurrentObjectMetadataId,
});

const foundFavorite = favorites?.find(
(favorite) => favorite.recordId === selectedRecordId,
);

const isFavorite = !!selectedRecordId && !!foundFavorite;

useEffect(() => {
if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) {
return;
}

addActionMenuEntry({
key: 'manage-favorites',
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
position,
Icon: isFavorite ? IconHeartOff : IconHeart,
onClick: () => {
if (isFavorite && isDefined(foundFavorite?.id)) {
deleteFavorite(foundFavorite.id);
} else if (isDefined(selectedRecord)) {
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
}
},
});

return () => {
removeActionMenuEntry('manage-favorites');
};
}, [
addActionMenuEntry,
createFavorite,
deleteFavorite,
foundFavorite?.id,
isFavorite,
objectMetadataItem,
position,
removeActionMenuEntry,
selectedRecord,
]);

return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';

const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect];

export const MultipleRecordsActionMenuEntriesSetter = () => {
return (
<>
{actionEffects.map((ActionEffect, index) => (
<ActionEffect key={index} position={index} />
))}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter';
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
import { useRecoilValue } from 'recoil';

export const RecordActionMenuEntriesSetter = () => {
const contextStoreTargetedRecordIds = useRecoilValue(
contextStoreTargetedRecordIdsState,
);

if (contextStoreTargetedRecordIds.length === 0) {
return null;
}

if (contextStoreTargetedRecordIds.length === 1) {
return <SingleRecordActionMenuEntriesSetter />;
}

return <MultipleRecordsActionMenuEntriesSetter />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';

export const SingleRecordActionMenuEntriesSetter = () => {
const actionEffects = [
ExportRecordsActionEffect,
DeleteRecordsActionEffect,
ManageFavoritesActionEffect,
];
return (
<>
{actionEffects.map((ActionEffect, index) => (
<ActionEffect key={index} position={index} />
))}
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';

import { ActionMenuBarEntry } from '@/action-menu/components/ActionMenuBarEntry';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
Expand All @@ -28,9 +28,13 @@ export const ActionMenuBar = () => {
);

const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentState,
actionMenuEntriesComponentSelector,
);

if (actionMenuEntries.length === 0) {
return null;
}

return (
<BottomBar
bottomBarId={`action-bar-${actionMenuId}`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';

export const ActionMenuConfirmationModals = () => {
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentState,
actionMenuEntriesComponentSelector,
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil';
import { PositionType } from '../types/PositionType';

import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
Expand Down Expand Up @@ -36,7 +36,7 @@ const StyledContainerActionMenuDropdown = styled.div<StyledContainerProps>`

export const ActionMenuDropdown = () => {
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentState,
actionMenuEntriesComponentSelector,
);

const actionMenuId = useAvailableComponentInstanceIdOrThrow(
Expand All @@ -50,6 +50,10 @@ export const ActionMenuDropdown = () => {
),
);

if (actionMenuEntries.length === 0) {
return null;
}

//TODO: remove this
const width = actionMenuEntries.some(
(actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites',
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit 3761fbf

Please sign in to comment.