Fix navigation drag drop indicator position (#18515)
Closes [#2295](https://github.com/twentyhq/core-team-issues/issues/2295) --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -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<string>, aboutDescription: string, providers: Array<string>, 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<string>, 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<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type InstallApplicationMutation = { __typename?: 'Mutation', installApplication: boolean };
|
||||
|
||||
export type InstallMarketplaceAppMutationVariables = Exact<{
|
||||
universalIdentifier: Scalars['String'];
|
||||
version?: InputMaybe<Scalars['String']>;
|
||||
@@ -6281,14 +6273,6 @@ export type UpgradeApplicationMutationVariables = Exact<{
|
||||
|
||||
export type UpgradeApplicationMutation = { __typename?: 'Mutation', upgradeApplication: boolean };
|
||||
|
||||
export type UploadAppTarballMutationVariables = Exact<{
|
||||
file: Scalars['Upload'];
|
||||
universalIdentifier?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
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<typeof useGetLogicFunctionSourceCodeQuery>;
|
||||
export type GetLogicFunctionSourceCodeLazyQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeLazyQuery>;
|
||||
export type GetLogicFunctionSourceCodeQueryResult = Apollo.QueryResult<GetLogicFunctionSourceCodeQuery, GetLogicFunctionSourceCodeQueryVariables>;
|
||||
export const InstallApplicationDocument = gql`
|
||||
mutation InstallApplication($appRegistrationId: String!, $version: String) {
|
||||
installApplication(appRegistrationId: $appRegistrationId, version: $version)
|
||||
}
|
||||
`;
|
||||
export type InstallApplicationMutationFn = Apollo.MutationFunction<InstallApplicationMutation, InstallApplicationMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<InstallApplicationMutation, InstallApplicationMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<InstallApplicationMutation, InstallApplicationMutationVariables>(InstallApplicationDocument, options);
|
||||
}
|
||||
export type InstallApplicationMutationHookResult = ReturnType<typeof useInstallApplicationMutation>;
|
||||
export type InstallApplicationMutationResult = Apollo.MutationResult<InstallApplicationMutation>;
|
||||
export type InstallApplicationMutationOptions = Apollo.BaseMutationOptions<InstallApplicationMutation, InstallApplicationMutationVariables>;
|
||||
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<typeof useUpgradeApplicationMutation>;
|
||||
export type UpgradeApplicationMutationResult = Apollo.MutationResult<UpgradeApplicationMutation>;
|
||||
export type UpgradeApplicationMutationOptions = Apollo.BaseMutationOptions<UpgradeApplicationMutation, UpgradeApplicationMutationVariables>;
|
||||
export const UploadAppTarballDocument = gql`
|
||||
mutation UploadAppTarball($file: Upload!, $universalIdentifier: String) {
|
||||
uploadAppTarball(file: $file, universalIdentifier: $universalIdentifier) {
|
||||
id
|
||||
universalIdentifier
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UploadAppTarballMutationFn = Apollo.MutationFunction<UploadAppTarballMutation, UploadAppTarballMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<UploadAppTarballMutation, UploadAppTarballMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UploadAppTarballMutation, UploadAppTarballMutationVariables>(UploadAppTarballDocument, options);
|
||||
}
|
||||
export type UploadAppTarballMutationHookResult = ReturnType<typeof useUploadAppTarballMutation>;
|
||||
export type UploadAppTarballMutationResult = Apollo.MutationResult<UploadAppTarballMutation>;
|
||||
export type UploadAppTarballMutationOptions = Apollo.BaseMutationOptions<UploadAppTarballMutation, UploadAppTarballMutationVariables>;
|
||||
export const FindManyMarketplaceAppsDocument = gql`
|
||||
query FindManyMarketplaceApps {
|
||||
findManyMarketplaceApps {
|
||||
|
||||
+29
-10
@@ -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<OnDragEndResponder>[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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+38
-28
@@ -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}
|
||||
/>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
<AnimatedExpandableContainer
|
||||
isExpanded={
|
||||
isNavigationSectionOpen || isAddToNavigationDropTargetVisible
|
||||
}
|
||||
dimension="height"
|
||||
mode="fit-content"
|
||||
containAnimation
|
||||
initial={false}
|
||||
>
|
||||
{isNavigationMenuInEditMode ? (
|
||||
<Suspense
|
||||
fallback={
|
||||
<WorkspaceSectionListEditModeFallback
|
||||
<StyledWorkspaceSectionContentGapOffset>
|
||||
<AnimatedExpandableContainer
|
||||
isExpanded={
|
||||
isNavigationSectionOpen || isAddToNavigationDropTargetVisible
|
||||
}
|
||||
dimension="height"
|
||||
mode="fit-content"
|
||||
containAnimation
|
||||
initial={false}
|
||||
>
|
||||
{isNavigationMenuInEditMode ? (
|
||||
<Suspense
|
||||
fallback={
|
||||
<WorkspaceSectionListEditModeFallback
|
||||
filteredItems={filteredItems}
|
||||
folderChildrenById={folderChildrenById}
|
||||
onActiveObjectMetadataItemClick={
|
||||
onActiveObjectMetadataItemClick
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<LazyWorkspaceSectionListDndKit
|
||||
filteredItems={filteredItems}
|
||||
getEditModeProps={getEditModeProps}
|
||||
folderChildrenById={folderChildrenById}
|
||||
selectedNavigationMenuItemId={selectedNavigationMenuItemId}
|
||||
onNavigationMenuItemClick={onNavigationMenuItemClick}
|
||||
onActiveObjectMetadataItemClick={
|
||||
onActiveObjectMetadataItemClick
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<LazyWorkspaceSectionListDndKit
|
||||
</Suspense>
|
||||
) : (
|
||||
<NavigationDrawerSectionForWorkspaceItemsListReadOnly
|
||||
filteredItems={filteredItems}
|
||||
getEditModeProps={getEditModeProps}
|
||||
folderChildrenById={folderChildrenById}
|
||||
selectedNavigationMenuItemId={selectedNavigationMenuItemId}
|
||||
onNavigationMenuItemClick={onNavigationMenuItemClick}
|
||||
onActiveObjectMetadataItemClick={onActiveObjectMetadataItemClick}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
<NavigationDrawerSectionForWorkspaceItemsListReadOnly
|
||||
filteredItems={filteredItems}
|
||||
folderChildrenById={folderChildrenById}
|
||||
onActiveObjectMetadataItemClick={onActiveObjectMetadataItemClick}
|
||||
/>
|
||||
)}
|
||||
</AnimatedExpandableContainer>
|
||||
)}
|
||||
</AnimatedExpandableContainer>
|
||||
</StyledWorkspaceSectionContentGapOffset>
|
||||
</NavigationDrawerSection>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-1
@@ -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`
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ const StyledList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.betweenSiblingsGap};
|
||||
padding-top: ${themeCssVariables.betweenSiblingsGap};
|
||||
`;
|
||||
|
||||
type NavigationDrawerSectionForWorkspaceItemsListReadOnlyProps = Pick<
|
||||
|
||||
Reference in New Issue
Block a user