fix: insert-before folder for workspace DnD and add-to-nav drags (#18789)
Closes [#2294](https://github.com/twentyhq/core-team-issues/issues/2294) https://github.com/user-attachments/assets/f7e0da72-dd08-46c4-92d7-be0453aaadd9 --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
+1
@@ -1,4 +1,5 @@
|
||||
export type DroppableData = {
|
||||
droppableId: string;
|
||||
index: number;
|
||||
insertBeforeItemId?: string;
|
||||
};
|
||||
|
||||
+1
@@ -5,4 +5,5 @@ export type SortableTargetDestination = {
|
||||
effectiveDropTargetId: string;
|
||||
isTargetFolder: boolean;
|
||||
dropTargetId: string;
|
||||
insertBeforeItemId?: string;
|
||||
};
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ export const NavigationMenuItemDisplay = ({
|
||||
onNavigationMenuItemClick,
|
||||
onActiveObjectMetadataItemClick,
|
||||
readOnly,
|
||||
orphanIndex,
|
||||
}: NavigationMenuItemDisplayProps) => {
|
||||
switch (item.type) {
|
||||
case NavigationMenuItemType.FOLDER:
|
||||
@@ -32,6 +33,7 @@ export const NavigationMenuItemDisplay = ({
|
||||
selectedNavigationMenuItemId={selectedNavigationMenuItemId}
|
||||
onNavigationMenuItemClick={onNavigationMenuItemClick}
|
||||
readOnly={readOnly}
|
||||
orphanIndex={orphanIndex}
|
||||
/>
|
||||
);
|
||||
case NavigationMenuItemType.LINK:
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
import { pointerIntersection } from '@dnd-kit/collision';
|
||||
import { useDroppable } from '@dnd-kit/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { getDndKitDropTargetId } from '@/navigation-menu-item/common/utils/getDndKitDropTargetId';
|
||||
import type { DroppableData } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDroppableData';
|
||||
import { getDndKitDropTargetId } from '@/navigation-menu-item/common/utils/getDndKitDropTargetId';
|
||||
|
||||
const StyledSlotWrapper = styled.div<{ $empty: boolean }>`
|
||||
min-height: 0;
|
||||
@@ -13,8 +14,7 @@ const StyledSlotWrapper = styled.div<{ $empty: boolean }>`
|
||||
`;
|
||||
|
||||
const SLOT_COLLISION_PRIORITY = 1;
|
||||
|
||||
export const FOLDER_HEADER_SLOT_COLLISION_PRIORITY = 2;
|
||||
export const FOLDER_HEADER_SLOT_COLLISION_PRIORITY = 4;
|
||||
|
||||
type NavigationMenuItemDroppableSlotProps = {
|
||||
droppableId: string;
|
||||
@@ -37,6 +37,7 @@ export const NavigationMenuItemDroppableSlot = ({
|
||||
id,
|
||||
disabled,
|
||||
collisionPriority,
|
||||
collisionDetector: pointerIntersection,
|
||||
data,
|
||||
});
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { pointerIntersection } from '@dnd-kit/collision';
|
||||
import { useDroppable } from '@dnd-kit/react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import type { DroppableData } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDroppableData';
|
||||
import { getDndKitDropTargetId } from '@/navigation-menu-item/common/utils/getDndKitDropTargetId';
|
||||
|
||||
const INSERT_BEFORE_ZONE_HEIGHT_PX = 5;
|
||||
|
||||
const StyledInsertBeforeZone = styled.div`
|
||||
height: ${INSERT_BEFORE_ZONE_HEIGHT_PX}px;
|
||||
left: 0;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -${INSERT_BEFORE_ZONE_HEIGHT_PX}px;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type NavigationMenuItemInsertBeforeDroppableZoneProps = {
|
||||
orphanDroppableId: string;
|
||||
orphanIndex: number;
|
||||
itemId: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const NavigationMenuItemInsertBeforeDroppableZone = ({
|
||||
orphanDroppableId,
|
||||
orphanIndex,
|
||||
itemId,
|
||||
disabled = false,
|
||||
}: NavigationMenuItemInsertBeforeDroppableZoneProps) => {
|
||||
const id = getDndKitDropTargetId(orphanDroppableId, orphanIndex);
|
||||
const data: DroppableData = {
|
||||
droppableId: orphanDroppableId,
|
||||
index: orphanIndex,
|
||||
insertBeforeItemId: itemId,
|
||||
};
|
||||
const { ref } = useDroppable({
|
||||
id,
|
||||
disabled,
|
||||
collisionPriority: 5,
|
||||
collisionDetector: pointerIntersection,
|
||||
data,
|
||||
});
|
||||
|
||||
return <StyledInsertBeforeZone ref={ref} />;
|
||||
};
|
||||
+11
-10
@@ -6,26 +6,27 @@ import { IconFolder, IconLink, useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/useEnterLayoutCustomizationMode';
|
||||
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/common/constants/AddToNavSourceDroppableId';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
|
||||
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
|
||||
import { getObjectMetadataIdsInDraft } from '@/navigation-menu-item/common/utils/getObjectMetadataIdsInDraft';
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/common/utils/getStandardObjectIconColor';
|
||||
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/common/utils/validateAndExtractWorkspaceFolderId';
|
||||
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/edit/folder/hooks/useAddFolderToNavigationMenuDraft';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/edit/hooks/useNavigationMenuItemsDraftState';
|
||||
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/edit/hooks/useOpenNavigationMenuItemInSidePanel';
|
||||
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/edit/link/hooks/useAddLinkToNavigationMenuDraft';
|
||||
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/edit/object/hooks/useAddObjectToNavigationMenuDraft';
|
||||
import { useAddRecordToNavigationMenuDraft } from '@/navigation-menu-item/edit/record/hooks/useAddRecordToNavigationMenuDraft';
|
||||
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/edit/view/hooks/useAddViewToNavigationMenuDraft';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/edit/hooks/useNavigationMenuItemsDraftState';
|
||||
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/edit/hooks/useOpenNavigationMenuItemInSidePanel';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/common/states/openNavigationMenuItemFolderIdsState';
|
||||
import { getObjectMetadataIdsInDraft } from '@/navigation-menu-item/common/utils/getObjectMetadataIdsInDraft';
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/common/utils/getStandardObjectIconColor';
|
||||
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
|
||||
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/common/utils/validateAndExtractWorkspaceFolderId';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
|
||||
|
||||
import { useStore } from 'jotai';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
export const useHandleAddToNavigationDrop = () => {
|
||||
const store = useStore();
|
||||
|
||||
+17
-23
@@ -175,37 +175,31 @@ export const useHandleNavigationMenuItemDragAndDrop = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceFolderId === destinationFolderId) {
|
||||
const sourceList = getSortedItems().filter((item) =>
|
||||
matchesFolderId(item, sourceFolderId),
|
||||
);
|
||||
|
||||
if (!sourceList.some((item) => item.id === draggableId)) return;
|
||||
|
||||
const insertBeforeIndex =
|
||||
result.insertBeforeItemId != null
|
||||
? sourceList.findIndex(
|
||||
(item) => item.id === result.insertBeforeItemId,
|
||||
)
|
||||
: -1;
|
||||
|
||||
await computeAndApplyReorder(
|
||||
draggableId,
|
||||
sourceList,
|
||||
insertBeforeIndex >= 0 ? insertBeforeIndex : destination.index,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const isSameFolder = sourceFolderId === destinationFolderId;
|
||||
|
||||
const destinationList = getSortedItems().filter((item) =>
|
||||
matchesFolderId(item, destinationFolderId),
|
||||
);
|
||||
|
||||
if (
|
||||
isSameFolder &&
|
||||
!destinationList.some((item) => item.id === draggableId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const insertBeforeIndex =
|
||||
result.insertBeforeItemId != null
|
||||
? destinationList.findIndex(
|
||||
(item) => item.id === result.insertBeforeItemId,
|
||||
)
|
||||
: -1;
|
||||
|
||||
await computeAndApplyReorder(
|
||||
draggableId,
|
||||
destinationList,
|
||||
destination.index,
|
||||
destinationFolderId ?? null,
|
||||
insertBeforeIndex >= 0 ? insertBeforeIndex : destination.index,
|
||||
isSameFolder ? undefined : (destinationFolderId ?? null),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+16
-15
@@ -6,10 +6,12 @@ import { type ComponentProps, useCallback, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/common/constants/AddToNavSourceDroppableId';
|
||||
import { NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG } from '@/navigation-menu-item/common/constants/NavigationMenuItemSectionDroppableConfig';
|
||||
import { NavigationSections } from '@/navigation-menu-item/common/constants/NavigationSections.constants';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import type { DraggableData } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDraggableData';
|
||||
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
|
||||
import type { SortableTargetDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitSortableTargetDestination';
|
||||
import type { NavigationMenuItemSection } from '@/navigation-menu-item/common/types/NavigationMenuItemSection';
|
||||
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
|
||||
import { extractFolderIdFromDroppableId } from '@/navigation-menu-item/common/utils/extractFolderIdFromDroppableId';
|
||||
@@ -24,9 +26,6 @@ import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks
|
||||
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/display/hooks/useSortedNavigationMenuItems';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/edit/hooks/useNavigationMenuItemsDraftState';
|
||||
|
||||
import { NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG } from '@/navigation-menu-item/common/constants/NavigationMenuItemSectionDroppableConfig';
|
||||
import type { SortableTargetDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitSortableTargetDestination';
|
||||
|
||||
type DragStartPayload = Parameters<
|
||||
NonNullable<
|
||||
ComponentProps<typeof DragDropProvider<DraggableData>>['onDragStart']
|
||||
@@ -234,16 +233,16 @@ export const useNavigationMenuItemDndKit = (
|
||||
isSortable(source) &&
|
||||
isSortable(target)
|
||||
) {
|
||||
setActiveDropTargetId(resolved.effectiveDropTargetId);
|
||||
setForbiddenDropTargetId(
|
||||
isAddToNavDrag
|
||||
? computeForbiddenTargetId(source, resolved, true)
|
||||
: computeForbiddenTargetId(source, resolved, false),
|
||||
);
|
||||
const forbiddenId = isAddToNavDrag
|
||||
? computeForbiddenTargetId(source, resolved, true)
|
||||
: computeForbiddenTargetId(source, resolved, false);
|
||||
|
||||
setActiveDropTargetId(resolved.dropTargetId);
|
||||
setForbiddenDropTargetId(forbiddenId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Branch 2: sortable-to-droppable-slot
|
||||
// Branch 2: sortable-to-droppable-slot (includes insert-before zones)
|
||||
if (resolved !== null && sourceIsSortable) {
|
||||
setActiveDropTargetId(resolved.effectiveDropTargetId);
|
||||
setAddToNavigationFallbackDestination(resolved.destination);
|
||||
@@ -257,7 +256,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
return;
|
||||
}
|
||||
|
||||
// Branch 3: add-to-nav drag
|
||||
// Branch 3: add-to-nav drag over droppable
|
||||
if (resolved !== null) {
|
||||
setAddToNavigationFallbackDestination(resolved.destination);
|
||||
setActiveDropTargetId(resolved.effectiveDropTargetId);
|
||||
@@ -326,20 +325,21 @@ export const useNavigationMenuItemDndKit = (
|
||||
droppableId: destGroup,
|
||||
});
|
||||
if (bothWorkspace) {
|
||||
const insertBeforeItemId = resolved.isTargetFolder
|
||||
? null
|
||||
: String(target?.id ?? '');
|
||||
const insertBeforeItemId =
|
||||
target?.id != null ? String(target.id) : undefined;
|
||||
applyWorkspaceReorder(
|
||||
draggableId,
|
||||
{ droppableId: initialGroup, index: initialIndex },
|
||||
resolved.destination,
|
||||
insertBeforeItemId || undefined,
|
||||
insertBeforeItemId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let destination: DropDestination | null = resolved?.destination ?? null;
|
||||
const insertBeforeItemId = resolved?.insertBeforeItemId;
|
||||
|
||||
if (
|
||||
destination == null &&
|
||||
isDefined(fallback) &&
|
||||
@@ -380,6 +380,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
index: data?.sourceIndex ?? 0,
|
||||
},
|
||||
destination,
|
||||
insertBeforeItemId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
||||
+13
-7
@@ -34,18 +34,24 @@ export const getDestinationFromSortableTarget = (
|
||||
isDefined(targetItem) && isNavigationMenuItemFolder(targetItem);
|
||||
const dropTargetId = getDndKitDropTargetId(destDroppableId, index);
|
||||
|
||||
const { folderHeaderPrefix } =
|
||||
const { folderHeaderPrefix, orphanDroppableId } =
|
||||
NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG[navigationMenuItemSection];
|
||||
|
||||
const isTopLevelOrphanGroup = destDroppableId === orphanDroppableId;
|
||||
|
||||
const effectiveDropTargetId = isTargetFolder
|
||||
? getDndKitDropTargetId(`${folderHeaderPrefix}${target.id}`, 0)
|
||||
: dropTargetId;
|
||||
const destination: DropDestination = {
|
||||
droppableId: isTargetFolder
|
||||
? `${folderHeaderPrefix}${target.id}`
|
||||
: destDroppableId,
|
||||
index: isTargetFolder ? 0 : index,
|
||||
};
|
||||
|
||||
const destination: DropDestination =
|
||||
isTargetFolder && isTopLevelOrphanGroup
|
||||
? { droppableId: destDroppableId, index }
|
||||
: isTargetFolder
|
||||
? {
|
||||
droppableId: `${folderHeaderPrefix}${String(target.id)}`,
|
||||
index: 0,
|
||||
}
|
||||
: { droppableId: destDroppableId, index };
|
||||
return {
|
||||
destination,
|
||||
effectiveDropTargetId,
|
||||
|
||||
+2
-1
@@ -38,7 +38,7 @@ export const resolveDropTarget = (
|
||||
);
|
||||
}
|
||||
if (isDroppableData(target.data)) {
|
||||
const { droppableId, index } = target.data;
|
||||
const { droppableId, index, insertBeforeItemId } = target.data;
|
||||
if (
|
||||
canNavigationMenuItemBeDroppedIn({
|
||||
navigationMenuItemSection,
|
||||
@@ -50,6 +50,7 @@ export const resolveDropTarget = (
|
||||
effectiveDropTargetId: String(target.id),
|
||||
isTargetFolder: false,
|
||||
dropTargetId: String(target.id),
|
||||
insertBeforeItemId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -30,6 +30,7 @@ type NavigationMenuItemFolderProps = Pick<
|
||||
| 'selectedNavigationMenuItemId'
|
||||
| 'onNavigationMenuItemClick'
|
||||
| 'readOnly'
|
||||
| 'orphanIndex'
|
||||
>;
|
||||
|
||||
export const NavigationMenuItemFolder = ({
|
||||
@@ -42,6 +43,7 @@ export const NavigationMenuItemFolder = ({
|
||||
selectedNavigationMenuItemId,
|
||||
onNavigationMenuItemClick,
|
||||
readOnly = false,
|
||||
orphanIndex,
|
||||
}: NavigationMenuItemFolderProps) => {
|
||||
const folderId = item.id;
|
||||
const folderName = item.name ?? 'Folder';
|
||||
@@ -88,6 +90,7 @@ export const NavigationMenuItemFolder = ({
|
||||
isDragging={isDragging}
|
||||
selectedNavigationMenuItemId={selectedNavigationMenuItemId}
|
||||
onNavigationMenuItemClick={onNavigationMenuItemClick}
|
||||
orphanIndex={orphanIndex}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
+19
-2
@@ -34,6 +34,7 @@ import {
|
||||
FOLDER_HEADER_SLOT_COLLISION_PRIORITY,
|
||||
NavigationMenuItemDroppableSlot,
|
||||
} from '@/navigation-menu-item/display/dnd/components/NavigationMenuItemDroppableSlot';
|
||||
import { NavigationMenuItemInsertBeforeDroppableZone } from '@/navigation-menu-item/display/dnd/components/NavigationMenuItemInsertBeforeDroppableZone';
|
||||
import { NavigationMenuItemSortableItem } from '@/navigation-menu-item/display/dnd/components/NavigationMenuItemSortableItem';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/display/dnd/hooks/useIsDropDisabledForSection';
|
||||
import { NavigationMenuItemFolderLayout } from '@/navigation-menu-item/display/folder/components/NavigationMenuItemFolderLayout';
|
||||
@@ -88,6 +89,7 @@ type NavigationMenuItemFolderDndProps = {
|
||||
isDragging: boolean;
|
||||
selectedNavigationMenuItemId?: string | null;
|
||||
onNavigationMenuItemClick?: (params: NavigationMenuItemClickParams) => void;
|
||||
orphanIndex?: number;
|
||||
};
|
||||
|
||||
export const NavigationMenuItemFolderDnd = ({
|
||||
@@ -102,6 +104,7 @@ export const NavigationMenuItemFolderDnd = ({
|
||||
isDragging: isDraggingProp,
|
||||
selectedNavigationMenuItemId,
|
||||
onNavigationMenuItemClick,
|
||||
orphanIndex,
|
||||
}: NavigationMenuItemFolderDndProps) => {
|
||||
const { t } = useLingui();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
@@ -243,8 +246,22 @@ export const NavigationMenuItemFolderDnd = ({
|
||||
/>
|
||||
);
|
||||
|
||||
const showInsertBeforeZone =
|
||||
isDragging && orphanIndex !== undefined && !isEditInPlace;
|
||||
|
||||
const wrappedHeader = (
|
||||
<div ref={setSortableDropTargetRef ?? undefined}>
|
||||
<div
|
||||
ref={setSortableDropTargetRef ?? undefined}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
{showInsertBeforeZone && (
|
||||
<NavigationMenuItemInsertBeforeDroppableZone
|
||||
orphanDroppableId={config.orphanDroppableId}
|
||||
orphanIndex={orphanIndex}
|
||||
itemId={folderId}
|
||||
disabled={dropDisabled}
|
||||
/>
|
||||
)}
|
||||
<NavigationMenuItemDroppableSlot
|
||||
droppableId={folderHeaderDroppableId}
|
||||
index={0}
|
||||
@@ -369,7 +386,7 @@ export const NavigationMenuItemFolderDnd = ({
|
||||
folderId={folderId}
|
||||
index={navigationMenuItems.length}
|
||||
sectionId={sectionId}
|
||||
compact={!(isWorkspace && isLayoutCustomizationModeEnabled)}
|
||||
compact={isCompact}
|
||||
dropTargetIdOverride={getDndKitDropTargetId(
|
||||
folderContentDroppableId,
|
||||
navigationMenuItems.length,
|
||||
|
||||
+9
-48
@@ -1,16 +1,12 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/common/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/isNavigationMenuItemFolder';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { getWorkspaceSidebarOrphanItemsInDisplayOrder } from '@/navigation-menu-item/display/utils/getWorkspaceSidebarOrphanItemsInDisplayOrder';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/display/folder/hooks/useNavigationMenuItemsByFolder';
|
||||
import { useNavigationMenuItemsData } from './useNavigationMenuItemsData';
|
||||
@@ -30,14 +26,6 @@ export const useNavigationMenuItemSectionItems = (): NavigationMenuItem[] => {
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const flatWorkspaceItems = workspaceNavigationMenuItems
|
||||
.filter((item) => !isDefined(item.folderId))
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
const processedItemsById = new Map(
|
||||
workspaceNavigationMenuItemsSorted.map((item) => [item.id, item]),
|
||||
);
|
||||
|
||||
const folderChildrenById = new Map(
|
||||
workspaceNavigationMenuItemsByFolder.map((folder) => [
|
||||
folder.id,
|
||||
@@ -45,40 +33,13 @@ export const useNavigationMenuItemSectionItems = (): NavigationMenuItem[] => {
|
||||
]),
|
||||
);
|
||||
|
||||
const flatItems: NavigationMenuItem[] = flatWorkspaceItems.reduce<
|
||||
NavigationMenuItem[]
|
||||
>((acc, item) => {
|
||||
if (isNavigationMenuItemFolder(item)) {
|
||||
acc.push({
|
||||
...item,
|
||||
icon: item.icon ?? FOLDER_ICON_DEFAULT,
|
||||
});
|
||||
} else {
|
||||
const validItem = processedItemsById.get(item.id);
|
||||
if (!isDefined(validItem)) {
|
||||
return acc;
|
||||
}
|
||||
if (validItem.type === NavigationMenuItemType.LINK) {
|
||||
acc.push(validItem);
|
||||
} else {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
validItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
if (
|
||||
isDefined(objectMetadataItem) &&
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
).canReadObjectRecords
|
||||
) {
|
||||
acc.push(validItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
const flatItems = getWorkspaceSidebarOrphanItemsInDisplayOrder({
|
||||
workspaceNavigationMenuItems,
|
||||
workspaceNavigationMenuItemsSorted,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
return flatItems.flatMap((item) =>
|
||||
item.type === NavigationMenuItemType.FOLDER
|
||||
|
||||
+1
@@ -21,4 +21,5 @@ export type NavigationMenuItemSectionContentProps = {
|
||||
navigationMenuItemId: string,
|
||||
) => void;
|
||||
readOnly?: boolean;
|
||||
orphanIndex?: number;
|
||||
};
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ export const WorkspaceSectionListDndKit = ({
|
||||
selectedNavigationMenuItemId={selectedNavigationMenuItemId}
|
||||
onNavigationMenuItemClick={onNavigationMenuItemClick}
|
||||
onActiveObjectMetadataItemClick={onActiveObjectMetadataItemClick}
|
||||
orphanIndex={index}
|
||||
/>
|
||||
</NavigationMenuItemSortableItem>
|
||||
</StyledListItemRow>
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/common/constants/FolderIconDefault';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/isNavigationMenuItemFolder';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/display/object/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { type ViewWithRelations } from '@/views/types/ViewWithRelations';
|
||||
|
||||
type GetWorkspaceSidebarOrphanItemsInDisplayOrderArgs = {
|
||||
workspaceNavigationMenuItems: NavigationMenuItem[];
|
||||
workspaceNavigationMenuItemsSorted: NavigationMenuItem[];
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
views: ViewWithRelations[];
|
||||
objectPermissionsByObjectMetadataId: Parameters<
|
||||
typeof getObjectPermissionsForObject
|
||||
>[0];
|
||||
};
|
||||
|
||||
export const getWorkspaceSidebarOrphanItemsInDisplayOrder = ({
|
||||
workspaceNavigationMenuItems,
|
||||
workspaceNavigationMenuItemsSorted,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: GetWorkspaceSidebarOrphanItemsInDisplayOrderArgs): NavigationMenuItem[] => {
|
||||
const flatWorkspaceItems = workspaceNavigationMenuItems
|
||||
.filter((item) => !isDefined(item.folderId))
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
const processedItemsById = new Map(
|
||||
workspaceNavigationMenuItemsSorted.map((item) => [item.id, item]),
|
||||
);
|
||||
|
||||
return flatWorkspaceItems.reduce<NavigationMenuItem[]>((acc, item) => {
|
||||
if (isNavigationMenuItemFolder(item)) {
|
||||
acc.push({
|
||||
...item,
|
||||
icon: item.icon ?? FOLDER_ICON_DEFAULT,
|
||||
});
|
||||
} else {
|
||||
const validItem = processedItemsById.get(item.id);
|
||||
if (!isDefined(validItem)) {
|
||||
return acc;
|
||||
}
|
||||
if (validItem.type === NavigationMenuItemType.LINK) {
|
||||
acc.push(validItem);
|
||||
} else {
|
||||
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
|
||||
validItem,
|
||||
objectMetadataItems,
|
||||
views,
|
||||
);
|
||||
if (
|
||||
isDefined(objectMetadataItem) &&
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
).canReadObjectRecords
|
||||
) {
|
||||
acc.push(validItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
Reference in New Issue
Block a user