Fix board drag and drop issues (#19005)
This PR solves multiple problems : - An infinite loop that was happening on board with initial and fetch more queries - Warning messages that get triggered when we drag and drop multiple times in a row - An attempt to fix an existing error in Sentry, that couldn't be reproduced for now.
This commit is contained in:
+1
-1
@@ -1,6 +1,7 @@
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
import { useListenToObjectRecordOperationBrowserEvent } from '@/browser-event/hooks/useListenToObjectRecordOperationBrowserEvent';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
import { useGetShouldInitializeRecordBoardForUpdateInputs } from '@/object-record/record-board/hooks/useGetShouldInitializeRecordBoardForUpdateInputs';
|
||||
import { useRemoveRecordsFromBoard } from '@/object-record/record-board/hooks/useRemoveRecordsFromBoard';
|
||||
import { useTriggerRecordBoardInitialQuery } from '@/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery';
|
||||
@@ -9,7 +10,6 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecordOperationBrowserEventDetail } from '@/browser-event/types/ObjectRecordOperationBrowserEventDetail';
|
||||
import { useAtomComponentFamilySelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorCallbackState';
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
|
||||
+39
-9
@@ -1,4 +1,5 @@
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState';
|
||||
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
|
||||
import { useEndRecordDrag } from '@/object-record/record-drag/hooks/useEndRecordDrag';
|
||||
import { useProcessBoardCardDrop } from '@/object-record/record-drag/hooks/useProcessBoardCardDrop';
|
||||
@@ -8,14 +9,14 @@ import { originalDragSelectionComponentState } from '@/object-record/record-drag
|
||||
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import {
|
||||
DragDropContext,
|
||||
type DragStart,
|
||||
type OnDragEndResponder,
|
||||
} from '@hello-pangea/dnd';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
export const RecordBoardDragDropContext = ({
|
||||
@@ -35,7 +36,7 @@ export const RecordBoardDragDropContext = ({
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const originalDragSelection = useAtomComponentStateCallbackState(
|
||||
const originalDragSelectionCallbackState = useAtomComponentStateCallbackState(
|
||||
originalDragSelectionComponentState,
|
||||
recordBoardId,
|
||||
);
|
||||
@@ -45,41 +46,70 @@ export const RecordBoardDragDropContext = ({
|
||||
|
||||
const { processBoardCardDrop } = useProcessBoardCardDrop();
|
||||
|
||||
const isRecordBoardDropProcessingCallbackState =
|
||||
useAtomComponentStateCallbackState(
|
||||
isRecordBoardDropProcessingComponentState,
|
||||
);
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(start: DragStart) => {
|
||||
const currentSelectedRecordIds = store.get(recordBoardSelectedRecordIds);
|
||||
|
||||
store.set(isRecordBoardDropProcessingCallbackState, true);
|
||||
|
||||
startRecordDrag(start, currentSelectedRecordIds);
|
||||
},
|
||||
[recordBoardSelectedRecordIds, startRecordDrag, store],
|
||||
[
|
||||
recordBoardSelectedRecordIds,
|
||||
startRecordDrag,
|
||||
store,
|
||||
isRecordBoardDropProcessingCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
const handleDragEnd: OnDragEndResponder = useCallback(
|
||||
(result) => {
|
||||
endRecordDrag();
|
||||
const originalDragSelection = store.get(
|
||||
originalDragSelectionCallbackState,
|
||||
);
|
||||
|
||||
if (!result.destination) return;
|
||||
if (!result.destination) {
|
||||
store.set(isRecordBoardDropProcessingCallbackState, false);
|
||||
endRecordDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
const existingRecordSorts = store.get(currentRecordSorts);
|
||||
|
||||
if (existingRecordSorts.length > 0) {
|
||||
store.set(isRecordBoardDropProcessingCallbackState, false);
|
||||
endRecordDrag();
|
||||
openModal(RECORD_INDEX_REMOVE_SORTING_MODAL_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
const originalSelection = store.get(originalDragSelection) as string[];
|
||||
try {
|
||||
processBoardCardDrop(result, originalDragSelection);
|
||||
} catch (error) {
|
||||
store.set(isRecordBoardDropProcessingCallbackState, false);
|
||||
endRecordDrag();
|
||||
|
||||
processBoardCardDrop(result, originalSelection);
|
||||
throw error;
|
||||
}
|
||||
|
||||
store.set(isRecordBoardDropProcessingCallbackState, false);
|
||||
endRecordDrag();
|
||||
},
|
||||
[
|
||||
processBoardCardDrop,
|
||||
originalDragSelection,
|
||||
endRecordDrag,
|
||||
currentRecordSorts,
|
||||
openModal,
|
||||
store,
|
||||
originalDragSelectionCallbackState,
|
||||
isRecordBoardDropProcessingCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+13
-14
@@ -7,8 +7,8 @@ import { RECORD_BOARD_COLUMN_PADDING_AND_BORDER_WIDTH } from '@/object-record/re
|
||||
|
||||
import { RECORD_BOARD_COLUMN_WIDTH } from '@/object-record/record-board/constants/RecordBoardColumnWidth';
|
||||
import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/constants/RecordBoardQueryPageSize';
|
||||
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
|
||||
import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState';
|
||||
import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordGroupsAreInInitialLoadingComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupsAreInInitialLoadingComponentState';
|
||||
@@ -38,12 +38,12 @@ export const RecordBoardFetchMoreInViewTriggerComponent = () => {
|
||||
const [recordBoardShouldFetchMore, setRecordBoardShouldFetchMore] =
|
||||
useAtomComponentState(recordBoardShouldFetchMoreComponentState);
|
||||
|
||||
const recordIndexRecordGroupsAreInInitialLoading = useAtomComponentStateValue(
|
||||
recordIndexRecordGroupsAreInInitialLoadingComponentState,
|
||||
const isDraggingRecord = useAtomComponentStateValue(
|
||||
isDraggingRecordComponentState,
|
||||
);
|
||||
|
||||
const recordBoardIsFetchingMore = useAtomComponentStateValue(
|
||||
recordBoardIsFetchingMoreComponentState,
|
||||
const recordIndexRecordGroupsAreInInitialLoading = useAtomComponentStateValue(
|
||||
recordIndexRecordGroupsAreInInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
const visibleRecordFields = useAtomComponentSelectorValue(
|
||||
@@ -76,22 +76,21 @@ export const RecordBoardFetchMoreInViewTriggerComponent = () => {
|
||||
1;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!recordIndexRecordGroupsAreInInitialLoading &&
|
||||
!recordBoardIsFetchingMore
|
||||
) {
|
||||
const newShouldFetchMore = inView;
|
||||
if (recordIndexRecordGroupsAreInInitialLoading || isDraggingRecord) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordBoardShouldFetchMore !== newShouldFetchMore) {
|
||||
setRecordBoardShouldFetchMore(newShouldFetchMore);
|
||||
}
|
||||
const newShouldFetchMore = inView;
|
||||
|
||||
if (recordBoardShouldFetchMore !== newShouldFetchMore) {
|
||||
setRecordBoardShouldFetchMore(newShouldFetchMore);
|
||||
}
|
||||
}, [
|
||||
recordBoardShouldFetchMore,
|
||||
setRecordBoardShouldFetchMore,
|
||||
inView,
|
||||
recordIndexRecordGroupsAreInInitialLoading,
|
||||
recordBoardIsFetchingMore,
|
||||
isDraggingRecord,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
+12
-9
@@ -3,8 +3,8 @@ import { useTriggerRecordBoardInitialQuery } from '@/object-record/record-board/
|
||||
import { lastRecordBoardQueryIdentifierComponentState } from '@/object-record/record-board/states/lastRecordBoardQueryIdentifierComponentState';
|
||||
import { lastRecordGroupIdsComponentState } from '@/object-record/record-board/states/lastRecordGroupIdsComponentState';
|
||||
import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState';
|
||||
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
|
||||
import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState';
|
||||
import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState';
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecordIndexGroupCommonQueryVariables } from '@/object-record/record-index/hooks/useRecordIndexGroupCommonQueryVariables';
|
||||
@@ -22,6 +22,10 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
export const RecordBoardQueryEffect = () => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const isDraggingRecord = useAtomComponentStateValue(
|
||||
isDraggingRecordComponentState,
|
||||
);
|
||||
|
||||
const [lastRecordBoardQueryIdentifier, setLastRecordBoardQueryIdentifier] =
|
||||
useAtomComponentState(lastRecordBoardQueryIdentifierComponentState);
|
||||
|
||||
@@ -51,14 +55,10 @@ export const RecordBoardQueryEffect = () => {
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
|
||||
const [recordBoardShouldFetchMore] = useAtomComponentState(
|
||||
const recordBoardShouldFetchMore = useAtomComponentStateValue(
|
||||
recordBoardShouldFetchMoreComponentState,
|
||||
);
|
||||
|
||||
const recordBoardIsFetchingMore = useAtomComponentStateValue(
|
||||
recordBoardIsFetchingMoreComponentState,
|
||||
);
|
||||
|
||||
const { triggerRecordBoardFetchMore } = useTriggerRecordBoardFetchMore();
|
||||
|
||||
const { triggerRecordBoardInitialQuery } =
|
||||
@@ -73,6 +73,10 @@ export const RecordBoardQueryEffect = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDraggingRecord) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!recordIndexRecordGroupsAreInInitialLoading &&
|
||||
(queryIdentifierHasChanged || recordGroupIdsHaveChanged)
|
||||
@@ -82,8 +86,7 @@ export const RecordBoardQueryEffect = () => {
|
||||
} else if (
|
||||
!recordIndexRecordGroupsAreInInitialLoading &&
|
||||
recordBoardShouldFetchMore &&
|
||||
!queryIdentifierHasChanged &&
|
||||
!recordBoardIsFetchingMore
|
||||
!queryIdentifierHasChanged
|
||||
) {
|
||||
triggerRecordBoardFetchMore();
|
||||
}
|
||||
@@ -96,11 +99,11 @@ export const RecordBoardQueryEffect = () => {
|
||||
scrollWrapperHTMLElement,
|
||||
recordIndexRecordGroupsAreInInitialLoading,
|
||||
recordBoardShouldFetchMore,
|
||||
recordBoardIsFetchingMore,
|
||||
triggerRecordBoardFetchMore,
|
||||
setLastRecordGroupIds,
|
||||
recordGroupIds,
|
||||
recordGroupIdsHaveChanged,
|
||||
isDraggingRecord,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
+3
-11
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RECORD_BOARD_FETCH_MORE_THROTTLING_WAIT_TIME_IN_MILLISECONDS_TO_AVOID_REACT_FREEZE } from '@/object-record/record-board/constants/RecordBoardFetchMoreThrottlingWaitTimeInMillisecondsToAvoidReactFreeze';
|
||||
@@ -7,7 +7,6 @@ import { RECORD_BOARD_QUERY_PAGE_SIZE } from '@/object-record/record-board/const
|
||||
|
||||
import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState';
|
||||
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
|
||||
import { recordBoardShouldFetchMoreComponentState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreComponentState';
|
||||
import { recordBoardShouldFetchMoreInColumnComponentFamilyState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreInColumnComponentFamilyState';
|
||||
import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
@@ -17,10 +16,10 @@ import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { computeRecordGroupOptionsFilter } from '@/object-record/record-group/utils/computeRecordGroupOptionsFilter';
|
||||
@@ -68,11 +67,6 @@ export const useTriggerRecordBoardFetchMore = () => {
|
||||
recordBoardIsFetchingMoreComponentState,
|
||||
);
|
||||
|
||||
const recordBoardShouldFetchMoreCallbackState =
|
||||
useAtomComponentStateCallbackState(
|
||||
recordBoardShouldFetchMoreComponentState,
|
||||
);
|
||||
|
||||
const triggerRecordBoardFetchMore = useCallback(async () => {
|
||||
const isAlreadyFetchingMore = store.get(recordBoardIsFetchingMore);
|
||||
|
||||
@@ -103,7 +97,6 @@ export const useTriggerRecordBoardFetchMore = () => {
|
||||
.map((recordGroupDefinition) => recordGroupDefinition.value);
|
||||
|
||||
if (!isNonEmptyArray(recordGroupValuesThatShouldBeFetched)) {
|
||||
store.set(recordBoardShouldFetchMoreCallbackState, false);
|
||||
cleanStateBeforeExit();
|
||||
|
||||
return;
|
||||
@@ -227,7 +220,6 @@ export const useTriggerRecordBoardFetchMore = () => {
|
||||
recordBoardIsFetchingMore,
|
||||
recordBoardCurrentGroupByQueryOffsetCallbackState,
|
||||
recordBoardShouldFetchMoreInColumnFamilyCallbackState,
|
||||
recordBoardShouldFetchMoreCallbackState,
|
||||
combinedFilters,
|
||||
recordIndexGroupFieldMetadataItem,
|
||||
]);
|
||||
|
||||
+2
-2
@@ -15,13 +15,13 @@ import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
|
||||
import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useCallback } from 'react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useTriggerRecordBoardInitialQuery = () => {
|
||||
|
||||
+18
-3
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
|
||||
@@ -10,10 +10,15 @@ import { RecordBoardCardMultiDragPreview } from '@/object-record/record-board/re
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState';
|
||||
import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState';
|
||||
import { DragAndDropLibraryLegacyReRenderBreaker } from '@/ui/drag-and-drop/components/DragAndDropReRenderBreaker';
|
||||
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
const StyledDraggableContainer = styled.div`
|
||||
const StyledDraggableContainer = styled.div<{
|
||||
isDragDisabled: boolean;
|
||||
}>`
|
||||
cursor: ${({ isDragDisabled }) => (isDragDisabled ? 'default' : 'grab')};
|
||||
position: relative;
|
||||
scroll-margin-left: 8px;
|
||||
scroll-margin-right: 8px;
|
||||
@@ -34,6 +39,10 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isRecordBoardDropProcessing = useAtomComponentStateValue(
|
||||
isRecordBoardDropProcessingComponentState,
|
||||
);
|
||||
|
||||
const { columnIndex } = useContext(RecordBoardColumnContext);
|
||||
|
||||
const isRecordBoardCardFocused = useAtomComponentFamilyStateValue(
|
||||
@@ -48,9 +57,15 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
<RecordBoardCardContext.Provider
|
||||
value={{ recordId, isRecordReadOnly, rowIndex, columnIndex }}
|
||||
>
|
||||
<Draggable key={recordId} draggableId={recordId} index={rowIndex}>
|
||||
<Draggable
|
||||
key={recordId}
|
||||
draggableId={recordId}
|
||||
index={rowIndex}
|
||||
isDragDisabled={isRecordBoardDropProcessing}
|
||||
>
|
||||
{(draggableProvided) => (
|
||||
<StyledDraggableContainer
|
||||
isDragDisabled={isRecordBoardDropProcessing}
|
||||
id={`record-board-card-${columnIndex}-${rowIndex}`}
|
||||
ref={draggableProvided?.innerRef}
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { Draggable, type DroppableProvided } from '@hello-pangea/dnd';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
|
||||
import { createAtomComponentState } from '@/ui/utilities/state/jotai/utils/createAtomComponentState';
|
||||
|
||||
export const isRecordBoardDropProcessingComponentState =
|
||||
createAtomComponentState<boolean>({
|
||||
key: 'isRecordBoardDropProcessingComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: RecordBoardComponentInstanceContext,
|
||||
});
|
||||
+21
@@ -6,9 +6,12 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { isRecordBoardDropProcessingComponentState } from '@/object-record/record-board/states/isRecordBoardDropProcessingComponentState';
|
||||
import { useUpdateDroppedRecordOnBoard } from '@/object-record/record-drag/hooks/useUpdateDroppedRecordOnBoard';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
export const useProcessBoardCardDrop = () => {
|
||||
const store = useStore();
|
||||
@@ -21,6 +24,21 @@ export const useProcessBoardCardDrop = () => {
|
||||
|
||||
const { updateDroppedRecordOnBoard } = useUpdateDroppedRecordOnBoard();
|
||||
|
||||
const isRecordBoardDropProcessingCallbackState =
|
||||
useAtomComponentStateCallbackState(
|
||||
isRecordBoardDropProcessingComponentState,
|
||||
);
|
||||
|
||||
// TODO: this is necessary to avoid race conditions when dragging right after a previous drag (~200ms to 500ms)
|
||||
// A way to fix this would be to have a proper optimistic logic on drop that doesn't just resets the whole board with trigger initial query but updates everything without waiting for the request return
|
||||
// Which is the problem here because it kind of destroys the existing columns that have more records than page size, and dnd library has issues computing drag when the underlying data change.
|
||||
const debouncedUpdateDropProcessing = useDebouncedCallback(
|
||||
(isPending: boolean) => {
|
||||
store.set(isRecordBoardDropProcessingCallbackState, isPending);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const processBoardCardDrop = useCallback(
|
||||
(boardCardDropResult: DropResult, selectedRecordIds: string[]) => {
|
||||
if (!isDefined(selectFieldMetadataItem)) return;
|
||||
@@ -38,12 +56,15 @@ export const useProcessBoardCardDrop = () => {
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
debouncedUpdateDropProcessing(false);
|
||||
},
|
||||
[
|
||||
store,
|
||||
selectFieldMetadataItem,
|
||||
recordIndexRecordIdsByGroupCallbackFamilyState,
|
||||
updateDroppedRecordOnBoard,
|
||||
debouncedUpdateDropProcessing,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { draggedRecordIdsComponentState } from '@/object-record/record-drag/states/draggedRecordIdsComponentState';
|
||||
import { isDraggingRecordComponentState } from '@/object-record/record-drag/states/isDraggingRecordComponentState';
|
||||
|
||||
+27
-15
@@ -1,6 +1,5 @@
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { extractRecordPositions } from '@/object-record/record-drag/utils/extractRecordPositions';
|
||||
import { recordGroupDefinitionsComponentSelector } from '@/object-record/record-group/states/selectors/recordGroupDefinitionsComponentSelector';
|
||||
@@ -8,6 +7,7 @@ import { type RecordGroupDefinition } from '@/object-record/record-group/types/R
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useCallback, useContext } from 'react';
|
||||
@@ -35,7 +35,10 @@ export const useUpdateDroppedRecordOnBoard = () => {
|
||||
{
|
||||
recordId,
|
||||
position: newPosition,
|
||||
}: { recordId: string; position?: number },
|
||||
}: {
|
||||
recordId: string;
|
||||
position?: number;
|
||||
},
|
||||
targetRecordGroupValue: RecordGroupDefinition['value'],
|
||||
) => {
|
||||
const initialRecord = store.get(
|
||||
@@ -50,18 +53,32 @@ export const useUpdateDroppedRecordOnBoard = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialRecordGroupValue = initialRecord[
|
||||
selectFieldMetadataItem.name
|
||||
] as string | undefined;
|
||||
let currentRecordIdsInInitialRecordGroup: string[] = [];
|
||||
|
||||
const initialRecordGroup = recordGroupDefinitions.find(
|
||||
findByProperty('value', initialRecordGroupValue),
|
||||
(recordGroupDefinition) => {
|
||||
const recordIdsInGroup = store.get(
|
||||
recordIndexRecordIdsByGroupCallbackFamilyState(
|
||||
recordGroupDefinition.id,
|
||||
),
|
||||
);
|
||||
|
||||
const recordIsInGroup = recordIdsInGroup.includes(recordId);
|
||||
|
||||
if (recordIsInGroup) {
|
||||
currentRecordIdsInInitialRecordGroup = recordIdsInGroup;
|
||||
}
|
||||
|
||||
return recordIsInGroup;
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDefined(initialRecordGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialRecordGroupId = initialRecordGroup.id;
|
||||
|
||||
const targetRecordGroup = recordGroupDefinitions.find(
|
||||
findByProperty('value', targetRecordGroupValue),
|
||||
);
|
||||
@@ -70,7 +87,6 @@ export const useUpdateDroppedRecordOnBoard = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialRecordGroupId = initialRecordGroup.id;
|
||||
const targetRecordGroupId = targetRecordGroup.id;
|
||||
|
||||
const movingInsideSameRecordGroup =
|
||||
@@ -82,26 +98,22 @@ export const useUpdateDroppedRecordOnBoard = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRecordIdsInInitialRecordGroup = store.get(
|
||||
recordIndexRecordIdsByGroupCallbackFamilyState(initialRecordGroupId),
|
||||
) as string[];
|
||||
|
||||
const positionOfDroppedRecordInInitialRecordIds =
|
||||
const indexOfDroppedRecordInInitialRecordGroup =
|
||||
currentRecordIdsInInitialRecordGroup.findIndex((id) => id === recordId);
|
||||
|
||||
let currentRecordIdsInTargetRecordGroup = store.get(
|
||||
recordIndexRecordIdsByGroupCallbackFamilyState(targetRecordGroupId),
|
||||
) as string[];
|
||||
|
||||
if (positionOfDroppedRecordInInitialRecordIds === -1) {
|
||||
if (indexOfDroppedRecordInInitialRecordGroup === -1) {
|
||||
throw new Error(
|
||||
`Cannot find record id in initial record group ids on drop, this should not happen`,
|
||||
`Cannot find record id in initial record group ids on drop, this should not happen, recordId: ${recordId}, initialRecordGroupId: ${initialRecordGroupId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const newInitialGroupRecordIds =
|
||||
currentRecordIdsInInitialRecordGroup.toSpliced(
|
||||
positionOfDroppedRecordInInitialRecordIds,
|
||||
indexOfDroppedRecordInInitialRecordGroup,
|
||||
1,
|
||||
);
|
||||
|
||||
|
||||
+4
-1
@@ -15,7 +15,10 @@ type ProcessGroupDropParams = {
|
||||
selectedRecordIds: string[];
|
||||
recordIdsByGroupFamilyState: any;
|
||||
onUpdateRecord: (
|
||||
update: { recordId: string; position?: number },
|
||||
update: {
|
||||
recordId: string;
|
||||
position?: number;
|
||||
},
|
||||
targetRecordGroupId: RecordGroupDefinition['value'],
|
||||
) => void;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user