Refactor navigation menu item folder handling
- Introduced new utility functions `doesFolderNavigationMenuItemMatchUrlForSelection` and `resolveFolderSelectedNavigationMenuItemIndex` to streamline the logic for matching folder navigation items with the current URL and determining the selected item index. - Updated `useNavigationMenuItemFolderOpenState` to utilize the new functions, enhancing clarity and maintainability of the navigation state management. - Removed deprecated logic related to active navigation item checks, contributing to a cleaner implementation. These changes improve the overall navigation handling within the folder structure of the menu.
This commit is contained in:
+11
-85
@@ -1,16 +1,14 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { activeNavigationItemState } from '@/navigation-menu-item/common/states/activeNavigationItemState';
|
||||
import { currentNavigationMenuItemFolderIdState } from '@/navigation-menu-item/common/states/currentNavigationMenuItemFolderIdState';
|
||||
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
|
||||
import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/common/utils/isLocationMatchingNavigationMenuItem';
|
||||
import { matchesRecordShowPathForObject } from '@/navigation-menu-item/common/utils/matchesRecordShowPathForObject';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { resolveFolderSelectedNavigationMenuItemIndex } from '@/navigation-menu-item/display/folder/utils/resolveFolderSelectedNavigationMenuItemIndex';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
|
||||
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
@@ -34,6 +32,7 @@ export const useNavigationMenuItemFolderOpenState = ({
|
||||
const isMobile = useIsMobile();
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
const views = useAtomStateValue(viewsSelector);
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsData();
|
||||
|
||||
const [openNavigationMenuItemFolderIds, setOpenNavigationMenuItemFolderIds] =
|
||||
useAtomState(openNavigationMenuItemFolderIdsState);
|
||||
@@ -76,89 +75,16 @@ export const useNavigationMenuItemFolderOpenState = ({
|
||||
|
||||
const activeNavigationItem = useAtomStateValue(activeNavigationItemState);
|
||||
|
||||
const explicitMatchIndex = isDefined(activeNavigationItem)
|
||||
? navigationMenuItems.findIndex(
|
||||
(item) => item.id === activeNavigationItem.navItemId,
|
||||
)
|
||||
: -1;
|
||||
|
||||
const activeNavigationObjectNameSingular =
|
||||
activeNavigationItem?.objectNameSingular;
|
||||
|
||||
const navigationMenuItemMatchesActiveObject = (
|
||||
navigationMenuItem: NavigationMenuItem,
|
||||
): boolean => {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
navigationMenuItem,
|
||||
const selectedNavigationMenuItemIndex =
|
||||
resolveFolderSelectedNavigationMenuItemIndex({
|
||||
navigationMenuItems,
|
||||
activeNavigationItem,
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
objectMetadataItem.nameSingular === activeNavigationObjectNameSingular
|
||||
);
|
||||
};
|
||||
|
||||
const isActiveNavigationItemObjectInFolder =
|
||||
isDefined(activeNavigationObjectNameSingular) &&
|
||||
navigationMenuItems.some(navigationMenuItemMatchesActiveObject);
|
||||
|
||||
const recordMatchIndex = navigationMenuItems.findIndex((item) => {
|
||||
if (item.type !== NavigationMenuItemType.RECORD) {
|
||||
return false;
|
||||
}
|
||||
const computedLink = getNavigationMenuItemComputedLink(
|
||||
item,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
return computedLink === currentPath;
|
||||
});
|
||||
|
||||
const urlMatchIndex = navigationMenuItems.findIndex((item) => {
|
||||
const computedLink = getNavigationMenuItemComputedLink(
|
||||
item,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
if (
|
||||
isLocationMatchingNavigationMenuItem(
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
item.type,
|
||||
computedLink,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.type === NavigationMenuItemType.OBJECT) {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
item,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
if (isDefined(objectMetadataItem)) {
|
||||
return matchesRecordShowPathForObject(
|
||||
currentPath,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const selectedNavigationMenuItemIndex = isActiveNavigationItemObjectInFolder
|
||||
? explicitMatchIndex !== -1
|
||||
? explicitMatchIndex
|
||||
: recordMatchIndex
|
||||
: urlMatchIndex;
|
||||
workspaceNavigationMenuItems,
|
||||
});
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/common/utils/isLocationMatchingNavigationMenuItem';
|
||||
import { matchesRecordShowPathForObject } from '@/navigation-menu-item/common/utils/matchesRecordShowPathForObject';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
type DoesFolderNavigationMenuItemMatchUrlForSelectionParams = {
|
||||
folderChildNavigationMenuItem: NavigationMenuItem;
|
||||
workspaceNavigationMenuItems: NavigationMenuItem[];
|
||||
currentPath: string;
|
||||
currentViewPath: string;
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
views: Pick<View, 'id' | 'objectMetadataId'>[];
|
||||
};
|
||||
|
||||
export const doesFolderNavigationMenuItemMatchUrlForSelection = ({
|
||||
folderChildNavigationMenuItem,
|
||||
workspaceNavigationMenuItems,
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
}: DoesFolderNavigationMenuItemMatchUrlForSelectionParams): boolean => {
|
||||
const computedLink = getNavigationMenuItemComputedLink(
|
||||
folderChildNavigationMenuItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
let locationMatches = isLocationMatchingNavigationMenuItem(
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
folderChildNavigationMenuItem.type,
|
||||
computedLink,
|
||||
);
|
||||
|
||||
if (
|
||||
!locationMatches &&
|
||||
folderChildNavigationMenuItem.type === NavigationMenuItemType.OBJECT
|
||||
) {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
folderChildNavigationMenuItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
if (isDefined(objectMetadataItem)) {
|
||||
locationMatches = matchesRecordShowPathForObject(
|
||||
currentPath,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!locationMatches) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const objectMetadataForFolderChild = getObjectMetadataForNavigationMenuItem(
|
||||
folderChildNavigationMenuItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataForFolderChild)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !workspaceNavigationMenuItems.some((workspaceItem) => {
|
||||
if (
|
||||
isDefined(workspaceItem.folderId) ||
|
||||
workspaceItem.type !== NavigationMenuItemType.OBJECT
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const topLevelComputedLink = getNavigationMenuItemComputedLink(
|
||||
workspaceItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
if (
|
||||
!isLocationMatchingNavigationMenuItem(
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
workspaceItem.type,
|
||||
topLevelComputedLink,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const objectMetadataForTopLevelItem =
|
||||
getObjectMetadataForNavigationMenuItem(
|
||||
workspaceItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
return (
|
||||
isDefined(objectMetadataForTopLevelItem) &&
|
||||
objectMetadataForTopLevelItem.id === objectMetadataForFolderChild.id
|
||||
);
|
||||
});
|
||||
};
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { doesFolderNavigationMenuItemMatchUrlForSelection } from '@/navigation-menu-item/display/folder/utils/doesFolderNavigationMenuItemMatchUrlForSelection';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { getNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/utils/getNavigationMenuItemComputedLink';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
type ActiveNavigationItemForFolderSelection = {
|
||||
navItemId: string;
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
type ResolveFolderSelectedNavigationMenuItemIndexParams = {
|
||||
navigationMenuItems: NavigationMenuItem[];
|
||||
activeNavigationItem: ActiveNavigationItemForFolderSelection | null;
|
||||
currentPath: string;
|
||||
currentViewPath: string;
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
views: Pick<View, 'id' | 'objectMetadataId'>[];
|
||||
workspaceNavigationMenuItems: NavigationMenuItem[];
|
||||
};
|
||||
|
||||
export const resolveFolderSelectedNavigationMenuItemIndex = ({
|
||||
navigationMenuItems,
|
||||
activeNavigationItem,
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
workspaceNavigationMenuItems,
|
||||
}: ResolveFolderSelectedNavigationMenuItemIndexParams): number => {
|
||||
const explicitMatchIndex = isDefined(activeNavigationItem)
|
||||
? navigationMenuItems.findIndex(
|
||||
(item) => item.id === activeNavigationItem.navItemId,
|
||||
)
|
||||
: -1;
|
||||
|
||||
const activeNavigationObjectNameSingular =
|
||||
activeNavigationItem?.objectNameSingular;
|
||||
|
||||
const isActiveNavigationItemObjectInFolder =
|
||||
isDefined(activeNavigationObjectNameSingular) &&
|
||||
navigationMenuItems.some((navigationMenuItem) => {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
navigationMenuItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
objectMetadataItem.nameSingular === activeNavigationObjectNameSingular
|
||||
);
|
||||
});
|
||||
|
||||
const recordMatchIndex = navigationMenuItems.findIndex((item) => {
|
||||
if (item.type !== NavigationMenuItemType.RECORD) {
|
||||
return false;
|
||||
}
|
||||
const computedLink = getNavigationMenuItemComputedLink(
|
||||
item,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
return computedLink === currentPath;
|
||||
});
|
||||
|
||||
const urlMatchIndex = navigationMenuItems.findIndex((folderChildItem) =>
|
||||
doesFolderNavigationMenuItemMatchUrlForSelection({
|
||||
folderChildNavigationMenuItem: folderChildItem,
|
||||
workspaceNavigationMenuItems,
|
||||
currentPath,
|
||||
currentViewPath,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
}),
|
||||
);
|
||||
|
||||
return isActiveNavigationItemObjectInFolder
|
||||
? explicitMatchIndex !== -1
|
||||
? explicitMatchIndex
|
||||
: recordMatchIndex
|
||||
: urlMatchIndex;
|
||||
};
|
||||
Reference in New Issue
Block a user