Refactor action menu (#7586)
Introduces effects to set the actionMenuEntries
This commit is contained in:
@ -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;
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 />;
|
||||||
|
};
|
||||||
@ -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} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ActionMenuBarEntry } from '@/action-menu/components/ActionMenuBarEntry';
|
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 { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
||||||
@ -28,9 +28,13 @@ export const ActionMenuBar = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const actionMenuEntries = useRecoilComponentValueV2(
|
const actionMenuEntries = useRecoilComponentValueV2(
|
||||||
actionMenuEntriesComponentState,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (actionMenuEntries.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomBar
|
<BottomBar
|
||||||
bottomBarId={`action-bar-${actionMenuId}`}
|
bottomBarId={`action-bar-${actionMenuId}`}
|
||||||
|
|||||||
@ -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';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const ActionMenuConfirmationModals = () => {
|
export const ActionMenuConfirmationModals = () => {
|
||||||
const actionMenuEntries = useRecoilComponentValueV2(
|
const actionMenuEntries = useRecoilComponentValueV2(
|
||||||
actionMenuEntriesComponentState,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { PositionType } from '../types/PositionType';
|
import { PositionType } from '../types/PositionType';
|
||||||
|
|
||||||
import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState';
|
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 { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
@ -36,7 +36,7 @@ const StyledContainerActionMenuDropdown = styled.div<StyledContainerProps>`
|
|||||||
|
|
||||||
export const ActionMenuDropdown = () => {
|
export const ActionMenuDropdown = () => {
|
||||||
const actionMenuEntries = useRecoilComponentValueV2(
|
const actionMenuEntries = useRecoilComponentValueV2(
|
||||||
actionMenuEntriesComponentState,
|
actionMenuEntriesComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||||
@ -50,6 +50,10 @@ export const ActionMenuDropdown = () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (actionMenuEntries.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: remove this
|
//TODO: remove this
|
||||||
const width = actionMenuEntries.some(
|
const width = actionMenuEntries.some(
|
||||||
(actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites',
|
(actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites',
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import { EmptyActionMenuEntriesEffect } from '@/action-menu/components/EmptyActionMenuEntriesEffect';
|
|
||||||
import { NonEmptyActionMenuEntriesEffect } from '@/action-menu/components/NonEmptyActionMenuEntriesEffect';
|
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export const ActionMenuEntriesProvider = () => {
|
|
||||||
//TODO: Refactor this
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{contextStoreCurrentObjectMetadataId ? (
|
|
||||||
<NonEmptyActionMenuEntriesEffect
|
|
||||||
contextStoreCurrentObjectMetadataId={
|
|
||||||
contextStoreCurrentObjectMetadataId
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<EmptyActionMenuEntriesEffect />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export const EmptyActionMenuEntriesEffect = () => {
|
|
||||||
const setActionMenuEntries = useSetRecoilComponentStateV2(
|
|
||||||
actionMenuEntriesComponentState,
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
setActionMenuEntries([]);
|
|
||||||
}, [setActionMenuEntries]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import { useComputeActionsBasedOnContextStore } from '@/action-menu/hooks/useComputeActionsBasedOnContextStore';
|
|
||||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export const NonEmptyActionMenuEntriesEffect = ({
|
|
||||||
contextStoreCurrentObjectMetadataId,
|
|
||||||
}: {
|
|
||||||
contextStoreCurrentObjectMetadataId: string;
|
|
||||||
}) => {
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
|
||||||
objectId: contextStoreCurrentObjectMetadataId,
|
|
||||||
});
|
|
||||||
const { availableActionsInContext } = useComputeActionsBasedOnContextStore({
|
|
||||||
objectMetadataItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setActionMenuEntries = useSetRecoilComponentStateV2(
|
|
||||||
actionMenuEntriesComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setActionMenuEntries(availableActionsInContext);
|
|
||||||
}, [availableActionsInContext, setActionMenuEntries]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@ -25,18 +25,28 @@ const meta: Meta<typeof ActionMenuBar> = {
|
|||||||
actionMenuEntriesComponentState.atomFamily({
|
actionMenuEntriesComponentState.atomFamily({
|
||||||
instanceId: 'story-action-menu',
|
instanceId: 'story-action-menu',
|
||||||
}),
|
}),
|
||||||
[
|
new Map([
|
||||||
{
|
[
|
||||||
label: 'Delete',
|
'delete',
|
||||||
Icon: IconTrash,
|
{
|
||||||
onClick: deleteMock,
|
key: 'delete',
|
||||||
},
|
label: 'Delete',
|
||||||
{
|
position: 0,
|
||||||
label: 'Mark as done',
|
Icon: IconTrash,
|
||||||
Icon: IconCheckbox,
|
onClick: deleteMock,
|
||||||
onClick: markAsDoneMock,
|
},
|
||||||
},
|
],
|
||||||
],
|
[
|
||||||
|
'markAsDone',
|
||||||
|
{
|
||||||
|
key: 'markAsDone',
|
||||||
|
label: 'Mark as done',
|
||||||
|
position: 1,
|
||||||
|
Icon: IconCheckbox,
|
||||||
|
onClick: markAsDoneMock,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
set(
|
set(
|
||||||
isBottomBarOpenedComponentState.atomFamily({
|
isBottomBarOpenedComponentState.atomFamily({
|
||||||
|
|||||||
@ -21,7 +21,9 @@ const markAsDoneMock = jest.fn();
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
entry: {
|
entry: {
|
||||||
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
|
position: 0,
|
||||||
Icon: IconTrash,
|
Icon: IconTrash,
|
||||||
onClick: deleteMock,
|
onClick: deleteMock,
|
||||||
},
|
},
|
||||||
@ -31,7 +33,9 @@ export const Default: Story = {
|
|||||||
export const WithDangerAccent: Story = {
|
export const WithDangerAccent: Story = {
|
||||||
args: {
|
args: {
|
||||||
entry: {
|
entry: {
|
||||||
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
|
position: 0,
|
||||||
Icon: IconTrash,
|
Icon: IconTrash,
|
||||||
onClick: deleteMock,
|
onClick: deleteMock,
|
||||||
accent: 'danger',
|
accent: 'danger',
|
||||||
@ -42,7 +46,9 @@ export const WithDangerAccent: Story = {
|
|||||||
export const WithInteraction: Story = {
|
export const WithInteraction: Story = {
|
||||||
args: {
|
args: {
|
||||||
entry: {
|
entry: {
|
||||||
|
key: 'markAsDone',
|
||||||
label: 'Mark as done',
|
label: 'Mark as done',
|
||||||
|
position: 0,
|
||||||
Icon: IconCheckbox,
|
Icon: IconCheckbox,
|
||||||
onClick: markAsDoneMock,
|
onClick: markAsDoneMock,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,23 +33,38 @@ const meta: Meta<typeof ActionMenuDropdown> = {
|
|||||||
actionMenuEntriesComponentState.atomFamily({
|
actionMenuEntriesComponentState.atomFamily({
|
||||||
instanceId: 'story-action-menu',
|
instanceId: 'story-action-menu',
|
||||||
}),
|
}),
|
||||||
[
|
new Map([
|
||||||
{
|
[
|
||||||
label: 'Delete',
|
'delete',
|
||||||
Icon: IconTrash,
|
{
|
||||||
onClick: deleteMock,
|
key: 'delete',
|
||||||
},
|
label: 'Delete',
|
||||||
{
|
position: 0,
|
||||||
label: 'Mark as done',
|
Icon: IconTrash,
|
||||||
Icon: IconCheckbox,
|
onClick: deleteMock,
|
||||||
onClick: markAsDoneMock,
|
},
|
||||||
},
|
],
|
||||||
{
|
[
|
||||||
label: 'Add to favorites',
|
'markAsDone',
|
||||||
Icon: IconHeart,
|
{
|
||||||
onClick: addToFavoritesMock,
|
key: 'markAsDone',
|
||||||
},
|
label: 'Mark as done',
|
||||||
],
|
position: 1,
|
||||||
|
Icon: IconCheckbox,
|
||||||
|
onClick: markAsDoneMock,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'addToFavorites',
|
||||||
|
{
|
||||||
|
key: 'addToFavorites',
|
||||||
|
label: 'Add to favorites',
|
||||||
|
position: 2,
|
||||||
|
Icon: IconHeart,
|
||||||
|
onClick: addToFavoritesMock,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
set(
|
set(
|
||||||
extractComponentState(
|
extractComponentState(
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||||
|
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
|
||||||
|
export const useActionMenuEntries = () => {
|
||||||
|
const setActionMenuEntries = useSetRecoilComponentStateV2(
|
||||||
|
actionMenuEntriesComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const addActionMenuEntry = (entry: ActionMenuEntry) => {
|
||||||
|
setActionMenuEntries(
|
||||||
|
(prevEntries) => new Map([...prevEntries, [entry.key, entry]]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeActionMenuEntry = (key: string) => {
|
||||||
|
setActionMenuEntries((prevEntries) => {
|
||||||
|
const newMap = new Map(prevEntries);
|
||||||
|
newMap.delete(key);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
addActionMenuEntry,
|
||||||
|
removeActionMenuEntry,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,150 +0,0 @@
|
|||||||
import { useHandleFavoriteButton } from '@/action-menu/hooks/useHandleFavoriteButton';
|
|
||||||
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
|
||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
|
||||||
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
|
|
||||||
import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData';
|
|
||||||
import {
|
|
||||||
displayedExportProgress,
|
|
||||||
useExportTableData,
|
|
||||||
} from '@/object-record/record-index/options/hooks/useExportTableData';
|
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import {
|
|
||||||
IconFileExport,
|
|
||||||
IconHeart,
|
|
||||||
IconHeartOff,
|
|
||||||
IconTrash,
|
|
||||||
isDefined,
|
|
||||||
} from 'twenty-ui';
|
|
||||||
|
|
||||||
export const useComputeActionsBasedOnContextStore = ({
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
|
||||||
}) => {
|
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
|
||||||
contextStoreTargetedRecordIdsState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
const { handleFavoriteButtonClick } = useHandleFavoriteButton(
|
|
||||||
contextStoreTargetedRecordIds,
|
|
||||||
objectMetadataItem,
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseTableDataParams = {
|
|
||||||
delayMs: 100,
|
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
recordIndexId: objectMetadataItem.namePlural,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { deleteTableData } = useDeleteTableData(baseTableDataParams);
|
|
||||||
|
|
||||||
const handleDeleteClick = useCallback(() => {
|
|
||||||
deleteTableData(contextStoreTargetedRecordIds);
|
|
||||||
}, [deleteTableData, contextStoreTargetedRecordIds]);
|
|
||||||
|
|
||||||
const { progress, download } = useExportTableData({
|
|
||||||
...baseTableDataParams,
|
|
||||||
filename: `${objectMetadataItem.nameSingular}.csv`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isRemote = objectMetadataItem.isRemote;
|
|
||||||
|
|
||||||
const numberOfSelectedRecords = contextStoreTargetedRecordIds.length;
|
|
||||||
|
|
||||||
const canDelete =
|
|
||||||
!isObjectMetadataReadOnly(objectMetadataItem) &&
|
|
||||||
numberOfSelectedRecords < DELETE_MAX_COUNT;
|
|
||||||
|
|
||||||
const menuActions: ActionMenuEntry[] = useMemo(
|
|
||||||
() =>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: displayedExportProgress(progress),
|
|
||||||
Icon: IconFileExport,
|
|
||||||
accent: 'default',
|
|
||||||
onClick: () => download(),
|
|
||||||
} satisfies ActionMenuEntry,
|
|
||||||
canDelete
|
|
||||||
? ({
|
|
||||||
label: 'Delete',
|
|
||||||
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'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
} satisfies ActionMenuEntry)
|
|
||||||
: undefined,
|
|
||||||
].filter(isDefined),
|
|
||||||
[
|
|
||||||
download,
|
|
||||||
progress,
|
|
||||||
canDelete,
|
|
||||||
handleDeleteClick,
|
|
||||||
isDeleteRecordsModalOpen,
|
|
||||||
numberOfSelectedRecords,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasOnlyOneRecordSelected = contextStoreTargetedRecordIds.length === 1;
|
|
||||||
|
|
||||||
const { favorites } = useFavorites();
|
|
||||||
|
|
||||||
const isFavorite =
|
|
||||||
isNonEmptyString(contextStoreTargetedRecordIds[0]) &&
|
|
||||||
!!favorites?.find(
|
|
||||||
(favorite) => favorite.recordId === contextStoreTargetedRecordIds[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
availableActionsInContext: [
|
|
||||||
...menuActions,
|
|
||||||
...(!isRemote && isFavorite && hasOnlyOneRecordSelected
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Remove from favorites',
|
|
||||||
Icon: IconHeartOff,
|
|
||||||
onClick: handleFavoriteButtonClick,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(!isRemote && !isFavorite && hasOnlyOneRecordSelected
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Add to favorites',
|
|
||||||
Icon: IconHeart,
|
|
||||||
onClick: handleFavoriteButtonClick,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const useHandleFavoriteButton = (
|
|
||||||
selectedRecordIds: string[],
|
|
||||||
objectMetadataItem: ObjectMetadataItem,
|
|
||||||
callback?: () => void,
|
|
||||||
) => {
|
|
||||||
const { createFavorite, favorites, deleteFavorite } = useFavorites();
|
|
||||||
|
|
||||||
const handleFavoriteButtonClick = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
() => {
|
|
||||||
if (selectedRecordIds.length > 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedRecordId = selectedRecordIds[0];
|
|
||||||
const selectedRecord = snapshot
|
|
||||||
.getLoadable(recordStoreFamilyState(selectedRecordId))
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
const foundFavorite = favorites?.find(
|
|
||||||
(favorite) => favorite.recordId === selectedRecordId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isFavorite = !!selectedRecordId && !!foundFavorite;
|
|
||||||
|
|
||||||
if (isFavorite) {
|
|
||||||
deleteFavorite(foundFavorite.id);
|
|
||||||
} else if (isDefined(selectedRecord)) {
|
|
||||||
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
|
|
||||||
}
|
|
||||||
callback?.();
|
|
||||||
},
|
|
||||||
[
|
|
||||||
callback,
|
|
||||||
createFavorite,
|
|
||||||
deleteFavorite,
|
|
||||||
favorites,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
selectedRecordIds,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
return { handleFavoriteButtonClick };
|
|
||||||
};
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||||
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
||||||
|
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const actionMenuEntriesComponentSelector = createComponentSelectorV2<
|
||||||
|
ActionMenuEntry[]
|
||||||
|
>({
|
||||||
|
key: 'actionMenuEntriesComponentSelector',
|
||||||
|
instanceContext: ActionMenuComponentInstanceContext,
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) =>
|
||||||
|
Array.from(
|
||||||
|
get(
|
||||||
|
actionMenuEntriesComponentState.atomFamily({ instanceId }),
|
||||||
|
).values(),
|
||||||
|
)
|
||||||
|
.filter(isDefined)
|
||||||
|
.sort((a, b) => a.position - b.position),
|
||||||
|
});
|
||||||
@ -3,9 +3,9 @@ import { createComponentStateV2 } from '@/ui/utilities/state/component-state/uti
|
|||||||
import { ActionMenuEntry } from '../types/ActionMenuEntry';
|
import { ActionMenuEntry } from '../types/ActionMenuEntry';
|
||||||
|
|
||||||
export const actionMenuEntriesComponentState = createComponentStateV2<
|
export const actionMenuEntriesComponentState = createComponentStateV2<
|
||||||
ActionMenuEntry[]
|
Map<string, ActionMenuEntry>
|
||||||
>({
|
>({
|
||||||
key: 'actionMenuEntriesComponentState',
|
key: 'actionMenuEntriesComponentState',
|
||||||
defaultValue: [],
|
defaultValue: new Map(),
|
||||||
componentInstanceContext: ActionMenuComponentInstanceContext,
|
componentInstanceContext: ActionMenuComponentInstanceContext,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import { IconComponent } from 'twenty-ui';
|
|||||||
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
||||||
|
|
||||||
export type ActionMenuEntry = {
|
export type ActionMenuEntry = {
|
||||||
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
position: number;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
accent?: MenuItemAccent;
|
accent?: MenuItemAccent;
|
||||||
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
onClick?: (event?: MouseEvent<HTMLElement>) => void;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
|
|
||||||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
@ -31,14 +30,19 @@ describe('useObjectMetadataItemById', () => {
|
|||||||
|
|
||||||
const { objectMetadataItem } = result.current;
|
const { objectMetadataItem } = result.current;
|
||||||
|
|
||||||
expect(objectMetadataItem.id).toBe(opportunityObjectMetadata.id);
|
expect(objectMetadataItem?.id).toBe(opportunityObjectMetadata.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when invalid ID is provided', async () => {
|
it('should return null when invalid ID is provided', async () => {
|
||||||
expect(() =>
|
const { result } = renderHook(
|
||||||
renderHook(() => useObjectMetadataItemById({ objectId: 'invalid-id' }), {
|
() => useObjectMetadataItemById({ objectId: 'invalid-id' }),
|
||||||
|
{
|
||||||
wrapper: Wrapper,
|
wrapper: Wrapper,
|
||||||
}),
|
},
|
||||||
).toThrow(ObjectMetadataItemNotFoundError);
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = result.current;
|
||||||
|
|
||||||
|
expect(objectMetadataItem).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useObjectMetadataItemById = ({
|
export const useObjectMetadataItemById = ({
|
||||||
objectId,
|
objectId,
|
||||||
}: {
|
}: {
|
||||||
objectId: string;
|
objectId: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
@ -16,7 +15,9 @@ export const useObjectMetadataItemById = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isDefined(objectMetadataItem)) {
|
if (!isDefined(objectMetadataItem)) {
|
||||||
throw new ObjectMetadataItemNotFoundError(objectId, objectMetadataItems);
|
return {
|
||||||
|
objectMetadataItem: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -24,11 +24,11 @@ import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-
|
|||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||||
|
|
||||||
|
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
||||||
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
||||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||||
import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown';
|
import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown';
|
||||||
import { ActionMenuEffect } from '@/action-menu/components/ActionMenuEffect';
|
import { ActionMenuEffect } from '@/action-menu/components/ActionMenuEffect';
|
||||||
import { ActionMenuEntriesProvider } from '@/action-menu/components/ActionMenuEntriesProvider';
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
@ -202,7 +202,7 @@ export const RecordIndexContainer = () => {
|
|||||||
value={{ instanceId: recordIndexId }}
|
value={{ instanceId: recordIndexId }}
|
||||||
>
|
>
|
||||||
<ActionMenuEffect />
|
<ActionMenuEffect />
|
||||||
<ActionMenuEntriesProvider />
|
<RecordActionMenuEntriesSetter />
|
||||||
<ActionMenuBar />
|
<ActionMenuBar />
|
||||||
<ActionMenuDropdown />
|
<ActionMenuDropdown />
|
||||||
<ActionMenuConfirmationModals />
|
<ActionMenuConfirmationModals />
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import { tableRowIdsComponentState } from '@/object-record/record-table/states/t
|
|||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
type UseDeleteTableDataOptions = Omit<UseTableDataOptions, 'callback'>;
|
type UseDeleteTableDataOptions = Pick<
|
||||||
|
UseTableDataOptions,
|
||||||
|
'objectNameSingular' | 'recordIndexId'
|
||||||
|
>;
|
||||||
|
|
||||||
export const useDeleteTableData = ({
|
export const useDeleteTableData = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
|
import { ComponentReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentReadOnlySelectorV2';
|
||||||
|
import { ComponentSelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentSelectorV2';
|
||||||
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
||||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { RecoilState, RecoilValueReadOnly, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
export const useRecoilComponentValueV2 = <StateType>(
|
export const useRecoilComponentValueV2 = <StateType>(
|
||||||
componentStateV2: ComponentStateV2<StateType>,
|
componentStateV2:
|
||||||
|
| ComponentStateV2<StateType>
|
||||||
|
| ComponentSelectorV2<StateType>
|
||||||
|
| ComponentReadOnlySelectorV2<StateType>,
|
||||||
instanceIdFromProps?: string,
|
instanceIdFromProps?: string,
|
||||||
) => {
|
) => {
|
||||||
const instanceContext = globalComponentInstanceContextMap.get(
|
const instanceContext = globalComponentInstanceContextMap.get(
|
||||||
@ -22,5 +27,18 @@ export const useRecoilComponentValueV2 = <StateType>(
|
|||||||
instanceIdFromProps,
|
instanceIdFromProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
return useRecoilValue(componentStateV2.atomFamily({ instanceId }));
|
let state: RecoilState<StateType> | RecoilValueReadOnly<StateType>;
|
||||||
|
|
||||||
|
if (componentStateV2.type === 'ComponentState') {
|
||||||
|
state = componentStateV2.atomFamily({ instanceId });
|
||||||
|
} else if (
|
||||||
|
componentStateV2.type === 'ComponentSelector' ||
|
||||||
|
componentStateV2.type === 'ComponentReadOnlySelector'
|
||||||
|
) {
|
||||||
|
state = componentStateV2.selectorFamily({ instanceId });
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid component state type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return useRecoilValue(state);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user