diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index b3c6171810..f23a3c1be3 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -6257,14 +6257,6 @@ export type GetLogicFunctionSourceCodeQuery = { __typename?: 'Query', getLogicFu export type MarketplaceAppFieldsFragment = { __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array, aboutDescription: string, providers: Array, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, isFeatured: boolean, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null }; -export type InstallApplicationMutationVariables = Exact<{ - appRegistrationId: Scalars['String']; - version?: InputMaybe; -}>; - - -export type InstallApplicationMutation = { __typename?: 'Mutation', installApplication: boolean }; - export type InstallMarketplaceAppMutationVariables = Exact<{ universalIdentifier: Scalars['String']; version?: InputMaybe; @@ -6281,14 +6273,6 @@ export type UpgradeApplicationMutationVariables = Exact<{ export type UpgradeApplicationMutation = { __typename?: 'Mutation', upgradeApplication: boolean }; -export type UploadAppTarballMutationVariables = Exact<{ - file: Scalars['Upload']; - universalIdentifier?: InputMaybe; -}>; - - -export type UploadAppTarballMutation = { __typename?: 'Mutation', uploadAppTarball: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string } }; - export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never; }>; @@ -11538,38 +11522,6 @@ export function useGetLogicFunctionSourceCodeLazyQuery(baseOptions?: Apollo.Lazy export type GetLogicFunctionSourceCodeQueryHookResult = ReturnType; export type GetLogicFunctionSourceCodeLazyQueryHookResult = ReturnType; export type GetLogicFunctionSourceCodeQueryResult = Apollo.QueryResult; -export const InstallApplicationDocument = gql` - mutation InstallApplication($appRegistrationId: String!, $version: String) { - installApplication(appRegistrationId: $appRegistrationId, version: $version) -} - `; -export type InstallApplicationMutationFn = Apollo.MutationFunction; - -/** - * __useInstallApplicationMutation__ - * - * To run a mutation, you first call `useInstallApplicationMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useInstallApplicationMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [installApplicationMutation, { data, loading, error }] = useInstallApplicationMutation({ - * variables: { - * appRegistrationId: // value for 'appRegistrationId' - * version: // value for 'version' - * }, - * }); - */ -export function useInstallApplicationMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(InstallApplicationDocument, options); - } -export type InstallApplicationMutationHookResult = ReturnType; -export type InstallApplicationMutationResult = Apollo.MutationResult; -export type InstallApplicationMutationOptions = Apollo.BaseMutationOptions; export const InstallMarketplaceAppDocument = gql` mutation InstallMarketplaceApp($universalIdentifier: String!, $version: String) { installMarketplaceApp( @@ -11640,42 +11592,6 @@ export function useUpgradeApplicationMutation(baseOptions?: Apollo.MutationHookO export type UpgradeApplicationMutationHookResult = ReturnType; export type UpgradeApplicationMutationResult = Apollo.MutationResult; export type UpgradeApplicationMutationOptions = Apollo.BaseMutationOptions; -export const UploadAppTarballDocument = gql` - mutation UploadAppTarball($file: Upload!, $universalIdentifier: String) { - uploadAppTarball(file: $file, universalIdentifier: $universalIdentifier) { - id - universalIdentifier - name - } -} - `; -export type UploadAppTarballMutationFn = Apollo.MutationFunction; - -/** - * __useUploadAppTarballMutation__ - * - * To run a mutation, you first call `useUploadAppTarballMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUploadAppTarballMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [uploadAppTarballMutation, { data, loading, error }] = useUploadAppTarballMutation({ - * variables: { - * file: // value for 'file' - * universalIdentifier: // value for 'universalIdentifier' - * }, - * }); - */ -export function useUploadAppTarballMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UploadAppTarballDocument, options); - } -export type UploadAppTarballMutationHookResult = ReturnType; -export type UploadAppTarballMutationResult = Apollo.MutationResult; -export type UploadAppTarballMutationOptions = Apollo.BaseMutationOptions; export const FindManyMarketplaceAppsDocument = gql` query FindManyMarketplaceApps { findManyMarketplaceApps { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts index edd52f6212..aa22602aba 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop.ts @@ -1,4 +1,5 @@ import { type OnDragEndResponder } from '@hello-pangea/dnd'; +import { useStore } from 'jotai'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; @@ -11,21 +12,15 @@ import { matchesWorkspaceFolderId, validateAndExtractWorkspaceFolderId, } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { isDefined } from 'twenty-shared/utils'; import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData'; export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => { + const store = useStore(); const { workspaceNavigationMenuItems } = usePrefetchedNavigationMenuItemsData(); - const isNavigationMenuInEditMode = useAtomStateValue( - isNavigationMenuInEditModeState, - ); - const navigationMenuItemsDraft = useAtomStateValue( - navigationMenuItemsDraftState, - ); const setNavigationMenuItemsDraft = useSetAtomState( navigationMenuItemsDraftState, ); @@ -47,7 +42,9 @@ export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => { }; const handleWorkspaceNavigationMenuItemDragAndDrop: OnDragEndResponder = ( - result, + result: Parameters[0] & { + insertBeforeItemId?: string | null; + }, ) => { const { destination, source, draggableId } = result; @@ -70,6 +67,12 @@ export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => { return; } + const navigationMenuItemsDraft = store.get( + navigationMenuItemsDraftState.atom, + ); + const isNavigationMenuInEditMode = store.get( + isNavigationMenuInEditModeState.atom, + ); if (!isNavigationMenuInEditMode || !navigationMenuItemsDraft) { return; } @@ -122,8 +125,24 @@ export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => { const listWithoutDragged = sourceList.filter( (item) => item.id !== draggableId, ); - const prevItem = listWithoutDragged[destination.index - 1]; - const nextItem = listWithoutDragged[destination.index]; + const sourceIndexInList = sourceList.findIndex( + (item) => item.id === draggableId, + ); + const insertBeforeIndex = + result.insertBeforeItemId != null + ? sourceList.findIndex( + (item) => item.id === result.insertBeforeItemId, + ) + : -1; + const destinationIndexInFullList = + insertBeforeIndex >= 0 ? insertBeforeIndex : destination.index; + const destIndexInListWithoutDragged = + sourceIndexInList < destinationIndexInFullList && + destinationIndexInFullList <= listWithoutDragged.length + ? destinationIndexInFullList - 1 + : destinationIndexInFullList; + const prevItem = listWithoutDragged[destIndexInListWithoutDragged - 1]; + const nextItem = listWithoutDragged[destIndexInListWithoutDragged]; const newPosition = getPositionBetween( prevItem?.position, nextItem?.position, diff --git a/packages/twenty-front/src/modules/navigation/hooks/useWorkspaceDndKit.ts b/packages/twenty-front/src/modules/navigation/hooks/useWorkspaceDndKit.ts index b3931c3d2e..04b9559a25 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useWorkspaceDndKit.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useWorkspaceDndKit.ts @@ -1,8 +1,8 @@ import { type DragDropProvider } from '@dnd-kit/react'; import { isSortable } from '@dnd-kit/react/sortable'; import type { ResponderProvided } from '@hello-pangea/dnd'; -import { type ComponentProps, useCallback, useState } from 'react'; import { useStore } from 'jotai'; +import { type ComponentProps, useCallback, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId'; @@ -96,6 +96,7 @@ export const useWorkspaceDndKit = (): { id: string, source: DropDestination, destination: DropDestination, + insertBeforeItemId?: string | null, ) => { const draggedItem = getNavItemById(id); const destFolderId = validateAndExtractWorkspaceFolderId( @@ -118,7 +119,11 @@ export const useWorkspaceDndKit = (): { ); const provided: ResponderProvided = { announce: () => {} }; handleWorkspaceNavigationMenuItemDragAndDrop( - { ...result, ...DROP_RESULT_OPTIONS }, + { + ...result, + ...DROP_RESULT_OPTIONS, + ...(insertBeforeItemId != null && { insertBeforeItemId }), + }, provided, ); }; @@ -284,10 +289,14 @@ export const useWorkspaceDndKit = (): { isWorkspaceDroppableId(initialGroupStr) && isWorkspaceDroppableId(destGroup); if (bothWorkspace) { + const insertBeforeItemId = resolved.isTargetFolder + ? null + : String(target?.id ?? ''); applyWorkspaceReorderIfAllowed( draggableId, { droppableId: initialGroupStr, index: initialIndex }, resolved.destination, + insertBeforeItemId || undefined, ); return; } diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx index 33763febff..134185f620 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx @@ -26,6 +26,8 @@ import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/us import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; +import { styled } from '@linaria/react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; const LazyWorkspaceSectionListDndKit = lazy(() => import( @@ -33,6 +35,10 @@ const LazyWorkspaceSectionListDndKit = lazy(() => ).then((m) => ({ default: m.WorkspaceSectionListDndKit })), ); +const StyledWorkspaceSectionContentGapOffset = styled.div` + margin-top: calc(-1 * ${themeCssVariables.betweenSiblingsGap}); +`; + type NavigationDrawerSectionForWorkspaceItemsProps = { sectionTitle: string; items: FlatWorkspaceItem[]; @@ -150,44 +156,48 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ isOpen={isNavigationSectionOpen} /> - - {isNavigationMenuInEditMode ? ( - + + {isNavigationMenuInEditMode ? ( + + } + > + - } - > - + ) : ( + - - ) : ( - - )} - + )} + + ); }; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx index bcf8d5a550..7cc9d99223 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListDndKit.tsx @@ -3,7 +3,6 @@ import { WorkspaceDndKitSortableItem } from '@/navigation-menu-item/components/W import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds'; import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType'; import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext'; -import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -11,6 +10,7 @@ import { styled } from '@linaria/react'; import { useContext } from 'react'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; import { NavigationDrawerSectionForWorkspaceItemContent } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItemContent'; import { WorkspaceOrphanDropTarget } from '@/object-metadata/components/WorkspaceOrphanDropTarget'; import { WorkspaceSectionAddMenuItemButton } from '@/object-metadata/components/WorkspaceSectionAddMenuItemButton'; @@ -20,6 +20,7 @@ const StyledList = styled.div` display: flex; flex-direction: column; gap: ${themeCssVariables.betweenSiblingsGap}; + padding-top: ${themeCssVariables.betweenSiblingsGap}; `; const StyledListItemRow = styled.div` diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx index 53e9156c43..d3ed513b0f 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItemsListReadOnly.tsx @@ -10,6 +10,7 @@ const StyledList = styled.div` display: flex; flex-direction: column; gap: ${themeCssVariables.betweenSiblingsGap}; + padding-top: ${themeCssVariables.betweenSiblingsGap}; `; type NavigationDrawerSectionForWorkspaceItemsListReadOnlyProps = Pick<