Refactored table virtualization real index state (#17074)
This PR refactors the table virtualization real index main state that was creating performance problems. The problem was that we kept track of all the real rows even if we only print 200 at a time in the application. This caused a crash when some users with 10k+ rows on a table tried to iterate over this state 10.000 times or more. The trade-off is that now we keep all real indices in a Map, and each virtual row uses a selector to go to this Map. This way if we want to reset the full Map, we just have to overwrite the base state that contains the map, and the 200 selectors will recompute. This happens for example when we delete a row. This now has a limited and negligible performance impact.
This commit is contained in:
+9
-5
@@ -1,12 +1,14 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
@@ -15,8 +17,10 @@ import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||
// TODO: does not work when scrolling while dragging and does not work if not paired with a network refetch right after
|
||||
// But it's sufficient right now for the main use case
|
||||
export const useTriggerTableWithoutGroupDragAndDropOptimisticUpdate = () => {
|
||||
const recordIdByRealIndexCallbackFamilyState =
|
||||
useRecoilComponentCallbackState(recordIdByRealIndexComponentFamilyState);
|
||||
const recordIdByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
);
|
||||
|
||||
const lastScrollPositionCallbackState = useRecoilComponentCallbackState(
|
||||
lastScrollPositionComponentState,
|
||||
@@ -64,7 +68,7 @@ export const useTriggerTableWithoutGroupDragAndDropOptimisticUpdate = () => {
|
||||
) {
|
||||
const recordIdAtRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIdByRealIndexCallbackFamilyState({ realIndex }),
|
||||
recordIdByRealIndexCallbackSelector(realIndex),
|
||||
);
|
||||
|
||||
if (!isDefined(recordIdAtRealIndex)) {
|
||||
@@ -115,7 +119,7 @@ export const useTriggerTableWithoutGroupDragAndDropOptimisticUpdate = () => {
|
||||
[
|
||||
lastScrollPositionCallbackState,
|
||||
loadRecordsToVirtualRows,
|
||||
recordIdByRealIndexCallbackFamilyState,
|
||||
recordIdByRealIndexCallbackSelector,
|
||||
scrollWrapperHTMLElement?.clientHeight,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
],
|
||||
|
||||
+7
-6
@@ -10,7 +10,8 @@ import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-tab
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
|
||||
@@ -53,9 +54,9 @@ export const useSetRecordTableData = ({
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordIdByRealIndexCallbackState =
|
||||
const recordIdByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
@@ -135,11 +136,11 @@ export const useSetRecordTableData = ({
|
||||
for (const [realIndex, recordId] of recordIds.entries()) {
|
||||
const currentRecordIdAtRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIdByRealIndexCallbackState({ realIndex }),
|
||||
recordIdByRealIndexCallbackSelector(realIndex),
|
||||
);
|
||||
|
||||
if (recordId !== currentRecordIdAtRealIndex) {
|
||||
set(recordIdByRealIndexCallbackState({ realIndex }), recordId);
|
||||
set(recordIdByRealIndexCallbackSelector(realIndex), recordId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +154,7 @@ export const useSetRecordTableData = ({
|
||||
unfocusRecordTableRow,
|
||||
setRecordTableHoverPosition,
|
||||
isRowSelectedFamilyState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackSelector,
|
||||
isRecordTableInitialLoadingCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import { RecordTableTr } from '@/object-record/record-table/record-table-row/com
|
||||
import { useIsTableRowSecondaryDragged } from '@/object-record/record-table/record-table-row/hooks/useIsRecordSecondaryDragged';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -148,8 +148,8 @@ export const RecordTableBodyVirtualizedDraggableClone = ({
|
||||
const theme = useTheme();
|
||||
|
||||
const recordId = useRecoilComponentFamilyValue(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
{ realIndex },
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
const { lastColumnWidth } = useRecordTableLastColumnWidthToFill();
|
||||
|
||||
+5
-4
@@ -9,7 +9,8 @@ import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
@@ -55,13 +56,13 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
|
||||
);
|
||||
|
||||
const nextRecordId = useRecoilComponentFamilyValue(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
{ realIndex: focusIndex + 1 },
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
focusIndex + 1,
|
||||
);
|
||||
|
||||
const isNextRecordIdFirstOfGroup = useRecoilComponentFamilyValue(
|
||||
isRecordIdFirstOfGroupComponentFamilySelector,
|
||||
{ recordId: nextRecordId ?? '' },
|
||||
nextRecordId,
|
||||
);
|
||||
|
||||
const isFocused = useRecoilComponentFamilyValue(
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { isRecordIdSecondaryDragMultipleComponentFamilyState } from '@/object-record/record-drag/states/isRecordIdSecondaryDragMultipleComponentFamilyState';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
|
||||
export const useIsTableRowSecondaryDragged = (recordId: string | null) => {
|
||||
export const useIsTableRowSecondaryDragged = (recordId: Nullable<string>) => {
|
||||
const isSecondaryDragged = useRecoilComponentFamilyValue(
|
||||
isRecordIdSecondaryDragMultipleComponentFamilyState,
|
||||
{ recordId: recordId ?? '' },
|
||||
|
||||
+9
-3
@@ -3,9 +3,11 @@ import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const isRecordIdFirstOfGroupComponentFamilySelector =
|
||||
createComponentFamilySelector<boolean, { recordId: string }>({
|
||||
createComponentFamilySelector<boolean, Nullable<string>>({
|
||||
key: 'isRecordIdFirstOfGroupComponentFamilySelector',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
get:
|
||||
@@ -19,6 +21,10 @@ export const isRecordIdFirstOfGroupComponentFamilySelector =
|
||||
|
||||
const hasRecordGroups = recordGroupIds.length > 0;
|
||||
|
||||
if (!isDefined(familyKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasRecordGroups) {
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
const recordIdsForThisGroup = get(
|
||||
@@ -28,7 +34,7 @@ export const isRecordIdFirstOfGroupComponentFamilySelector =
|
||||
}),
|
||||
);
|
||||
|
||||
if (recordIdsForThisGroup[0] === familyKey.recordId) {
|
||||
if (recordIdsForThisGroup[0] === familyKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +45,7 @@ export const isRecordIdFirstOfGroupComponentFamilySelector =
|
||||
}),
|
||||
);
|
||||
|
||||
if (allRecordIds[0] === familyKey.recordId) {
|
||||
if (allRecordIds[0] === familyKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -3,8 +3,9 @@ import { getLabelIdentifierFieldValue } from '@/object-metadata/utils/getLabelId
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilySelector';
|
||||
import { realIndexByVirtualIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/realIndexByVirtualIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -50,8 +51,13 @@ export const RecordTableRowVirtualizedDebugRowHelper = ({
|
||||
);
|
||||
|
||||
const recordId = useRecoilComponentFamilyValue(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
{ realIndex },
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
const dataLoadingStatus = useRecoilComponentFamilyValue(
|
||||
dataLoadingStatusByRealIndexComponentFamilySelector,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
const pixelsFromTop =
|
||||
@@ -85,6 +91,9 @@ export const RecordTableRowVirtualizedDebugRowHelper = ({
|
||||
id:
|
||||
{recordId}
|
||||
</StyledDebugColumn>
|
||||
<StyledDebugColumn width={100}>
|
||||
status :{isDefined(dataLoadingStatus) ? dataLoadingStatus : 'undefined'}
|
||||
</StyledDebugColumn>
|
||||
</StyledDebugRow>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { RecordTableRowVirtualizedSkeleton } from '@/object-record/record-table/virtualization/components/RecordTableRowVirtualizedSkeleton';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -25,7 +25,7 @@ export const RecordTableRowVirtualizedFullData = ({
|
||||
}: RecordTableRowVirtualizedFullDataProps) => {
|
||||
const isFocused = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
realIndex ?? 0,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
const isRowFocusActive = useRecoilComponentValue(
|
||||
@@ -33,8 +33,8 @@ export const RecordTableRowVirtualizedFullData = ({
|
||||
);
|
||||
|
||||
const recordId = useRecoilComponentFamilyValue(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
{ realIndex },
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
if (!isDefined(recordId)) {
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import { RecordTableRowVirtualizedFullData } from '@/object-record/record-table/virtualization/components/RecordTableRowVirtualizedFullData';
|
||||
|
||||
import { RecordTableRowVirtualizedSkeleton } from '@/object-record/record-table/virtualization/components/RecordTableRowVirtualizedSkeleton';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilyState';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilySelector';
|
||||
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
|
||||
@@ -13,11 +13,11 @@ export const RecordTableRowVirtualizedRouterLevel2 = ({
|
||||
realIndex,
|
||||
}: RecordTableRowVirtualizedRouterLevel2Props) => {
|
||||
const dataLoadingStatus = useRecoilComponentFamilyValue(
|
||||
dataLoadingStatusByRealIndexComponentFamilyState,
|
||||
{ realIndex },
|
||||
dataLoadingStatusByRealIndexComponentFamilySelector,
|
||||
realIndex,
|
||||
);
|
||||
|
||||
if (dataLoadingStatus === null) {
|
||||
if (dataLoadingStatus !== 'loaded') {
|
||||
return <RecordTableRowVirtualizedSkeleton />;
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -3,22 +3,22 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilySelector';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
|
||||
export const useLoadRecordsToVirtualRows = () => {
|
||||
const recordIdByRealIndexCallbackState =
|
||||
const recordIdByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
);
|
||||
|
||||
const dataLoadingStatusByRealIndexCallbackState =
|
||||
useRecoilComponentCallbackState(
|
||||
dataLoadingStatusByRealIndexComponentFamilyState,
|
||||
const dataLoadingStatusByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
dataLoadingStatusByRealIndexComponentFamilySelector,
|
||||
);
|
||||
|
||||
const recordIndexAllRecordIdsSelector = useRecoilComponentCallbackState(
|
||||
@@ -52,15 +52,15 @@ export const useLoadRecordsToVirtualRows = () => {
|
||||
|
||||
const currentRecordIdAtRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIdByRealIndexCallbackState({ realIndex }),
|
||||
recordIdByRealIndexCallbackSelector(realIndex),
|
||||
);
|
||||
|
||||
if (record.id !== currentRecordIdAtRealIndex) {
|
||||
set(recordIdByRealIndexCallbackState({ realIndex }), record.id);
|
||||
set(recordIdByRealIndexCallbackSelector(realIndex), record.id);
|
||||
}
|
||||
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState({ realIndex }),
|
||||
dataLoadingStatusByRealIndexCallbackSelector(realIndex),
|
||||
'loaded',
|
||||
);
|
||||
}
|
||||
@@ -85,8 +85,8 @@ export const useLoadRecordsToVirtualRows = () => {
|
||||
set(recordIndexAllRecordIdsSelector, newAllRecordIds);
|
||||
},
|
||||
[
|
||||
recordIdByRealIndexCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackSelector,
|
||||
dataLoadingStatusByRealIndexCallbackSelector,
|
||||
recordIndexAllRecordIdsSelector,
|
||||
isRowSelectedCallbackState,
|
||||
hasUserSelectedAllRowsCallbackState,
|
||||
|
||||
+95
-3
@@ -4,11 +4,18 @@ import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/ho
|
||||
|
||||
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
|
||||
import { useTriggerFetchPages } from '@/object-record/record-table/virtualization/hooks/useTriggerFetchPages';
|
||||
import { dataLoadingStatusByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentState';
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
export const useResetVirtualizationBecauseDataChanged = (
|
||||
@@ -18,6 +25,8 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
|
||||
const params = useFindManyRecordIndexTableParams(objectNameSingular);
|
||||
|
||||
// TODO: we could optimize this by using an aggregate or using only id: true in recordGqlFields
|
||||
@@ -43,18 +52,101 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
|
||||
const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages();
|
||||
|
||||
const lastScrollPositionCallbackState = useRecoilComponentCallbackState(
|
||||
lastScrollPositionComponentState,
|
||||
);
|
||||
|
||||
const recordIdByRealIndexCallbackState = useRecoilComponentCallbackState(
|
||||
recordIdByRealIndexComponentState,
|
||||
);
|
||||
|
||||
const dataLoadingStatusByRealIndexCallbackState =
|
||||
useRecoilComponentCallbackState(dataLoadingStatusByRealIndexComponentState);
|
||||
|
||||
const resetVirtualization = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({ set, snapshot }) =>
|
||||
async () => {
|
||||
const { totalCount } = await findManyRecordsLazy();
|
||||
|
||||
const tableScrollWrapperHeight =
|
||||
scrollWrapperHTMLElement?.clientHeight ?? 0;
|
||||
|
||||
const lastScrollPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
lastScrollPositionCallbackState,
|
||||
);
|
||||
|
||||
const totalNumberOfRecordsToVirtualize =
|
||||
getSnapshotValue(
|
||||
snapshot,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
) ?? 0;
|
||||
|
||||
const {
|
||||
firstRealIndexInOverscanWindow,
|
||||
lastRealIndexInOverscanWindow,
|
||||
} = getVirtualizationOverscanWindow(
|
||||
lastScrollPosition,
|
||||
tableScrollWrapperHeight,
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
);
|
||||
|
||||
const recordIdByRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIdByRealIndexCallbackState,
|
||||
);
|
||||
|
||||
const dataLoadingStatusByRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
);
|
||||
|
||||
const lengthOfOverscanWindow =
|
||||
lastRealIndexInOverscanWindow - firstRealIndexInOverscanWindow + 1;
|
||||
|
||||
const newRecordIdByRealIndex = new Map<number, string>();
|
||||
const newDataLoadingStatusByRealIndex = new Map<
|
||||
number,
|
||||
'loaded' | 'not-loaded'
|
||||
>();
|
||||
|
||||
for (let i = 0; i < lengthOfOverscanWindow; i++) {
|
||||
const realIndex = firstRealIndexInOverscanWindow + i;
|
||||
|
||||
const existingRecordId = recordIdByRealIndex.get(realIndex);
|
||||
|
||||
if (isDefined(existingRecordId)) {
|
||||
newRecordIdByRealIndex.set(realIndex, existingRecordId);
|
||||
}
|
||||
|
||||
const existingDataLoadingStatus =
|
||||
dataLoadingStatusByRealIndex.get(realIndex);
|
||||
|
||||
if (isDefined(existingDataLoadingStatus)) {
|
||||
newDataLoadingStatusByRealIndex.set(
|
||||
realIndex,
|
||||
existingDataLoadingStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set(recordIdByRealIndexCallbackState, newRecordIdByRealIndex);
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
newDataLoadingStatusByRealIndex,
|
||||
);
|
||||
|
||||
set(dataPagesLoadedCallbackState, []);
|
||||
set(totalNumberOfRecordsToVirtualizeCallbackState, totalCount);
|
||||
},
|
||||
[
|
||||
dataPagesLoadedCallbackState,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
findManyRecordsLazy,
|
||||
scrollWrapperHTMLElement?.clientHeight,
|
||||
lastScrollPositionCallbackState,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
dataPagesLoadedCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+13
-21
@@ -14,12 +14,12 @@ import { useReapplyRowSelection } from '@/object-record/record-table/virtualizat
|
||||
|
||||
import { useResetTableFocuses } from '@/object-record/record-table/virtualization/hooks/useResetTableFocuses';
|
||||
import { useResetVirtualizedRowTreadmill } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizedRowTreadmill';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilyState';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilySelector';
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
import { isInitializingVirtualTableDataLoadingComponentState } from '@/object-record/record-table/virtualization/states/isInitializingVirtualTableDataLoadingComponentState';
|
||||
import { lastRealIndexSetComponentState } from '@/object-record/record-table/virtualization/states/lastRealIndexSetComponentState';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { recordIdByRealIndexComponentFamilySelector } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilySelector';
|
||||
import { scrollAtRealIndexComponentState } from '@/object-record/record-table/virtualization/states/scrollAtRealIndexComponentState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
@@ -56,14 +56,14 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
|
||||
const recordIdByRealIndexCallbackState =
|
||||
const recordIdByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
recordIdByRealIndexComponentFamilySelector,
|
||||
);
|
||||
|
||||
const dataLoadingStatusByRealIndexCallbackState =
|
||||
useRecoilComponentCallbackState(
|
||||
dataLoadingStatusByRealIndexComponentFamilyState,
|
||||
const dataLoadingStatusByRealIndexCallbackSelector =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
dataLoadingStatusByRealIndexComponentFamilySelector,
|
||||
);
|
||||
|
||||
const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState(
|
||||
@@ -144,20 +144,12 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
records = SIGN_IN_BACKGROUND_MOCK_COMPANIES;
|
||||
totalCount = SIGN_IN_BACKGROUND_MOCK_COMPANIES.length;
|
||||
} else {
|
||||
for (const [index] of currentRecordIds.entries()) {
|
||||
for (const [realIndex] of currentRecordIds.entries()) {
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState({
|
||||
realIndex: index,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
recordIdByRealIndexCallbackState({
|
||||
realIndex: index,
|
||||
}),
|
||||
null,
|
||||
dataLoadingStatusByRealIndexCallbackSelector(realIndex),
|
||||
'not-loaded',
|
||||
);
|
||||
set(recordIdByRealIndexCallbackSelector(realIndex), undefined);
|
||||
}
|
||||
|
||||
const { records: findManyRecords, totalCount: findManyTotalCount } =
|
||||
@@ -215,8 +207,8 @@ export const useTriggerInitialRecordTableDataLoad = () => {
|
||||
setIsRecordTableScrolledVertically,
|
||||
scrollTableToPosition,
|
||||
findManyRecordsLazy,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackSelector,
|
||||
recordIdByRealIndexCallbackSelector,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
upsertRecordsInStore,
|
||||
loadRecordsToVirtualRows,
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { dataLoadingStatusByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentState';
|
||||
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
||||
import { type DefaultValue } from 'recoil';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const dataLoadingStatusByRealIndexComponentFamilySelector =
|
||||
createComponentFamilySelector<
|
||||
'loaded' | 'not-loaded' | undefined,
|
||||
Nullable<number>
|
||||
>({
|
||||
key: 'dataLoadingStatusByRealIndexComponentFamilySelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId, familyKey }) =>
|
||||
({ get }) => {
|
||||
const realIndex = familyKey;
|
||||
|
||||
const dataLoadingStatusByRealIndex = get(
|
||||
dataLoadingStatusByRealIndexComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(realIndex)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dataLoadingStatus = dataLoadingStatusByRealIndex.get(realIndex);
|
||||
|
||||
return dataLoadingStatus;
|
||||
},
|
||||
set:
|
||||
({ familyKey, instanceId }) =>
|
||||
(
|
||||
{ get, set },
|
||||
newValue: 'loaded' | 'not-loaded' | DefaultValue | undefined,
|
||||
) => {
|
||||
if (!isDefined(familyKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actualDataLoadingStatusByRealIndex = get(
|
||||
dataLoadingStatusByRealIndexComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const newDataLoadingStatusByRealIndex = new Map(
|
||||
actualDataLoadingStatusByRealIndex,
|
||||
);
|
||||
|
||||
if (newValue === 'loaded') {
|
||||
newDataLoadingStatusByRealIndex.set(familyKey, 'loaded');
|
||||
} else {
|
||||
newDataLoadingStatusByRealIndex.set(familyKey, 'not-loaded');
|
||||
}
|
||||
|
||||
set(
|
||||
dataLoadingStatusByRealIndexComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
newDataLoadingStatusByRealIndex,
|
||||
);
|
||||
},
|
||||
});
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
|
||||
|
||||
export const dataLoadingStatusByRealIndexComponentFamilyState =
|
||||
createComponentFamilyState<'loaded' | null, { realIndex: number | null }>({
|
||||
key: 'dataLoadingStatusByRealIndexComponentFamilyState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: null,
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const dataLoadingStatusByRealIndexComponentState = createComponentState<
|
||||
Map<number, 'loaded' | 'not-loaded'>
|
||||
>({
|
||||
key: 'dataLoadingStatusByRealIndexComponentState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: new Map(),
|
||||
});
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const realIndexByRecordIdComponentSelector = createComponentSelector<{
|
||||
realIndexByRecordIdMap: Map<string, number>;
|
||||
}>({
|
||||
key: 'realIndexByRecordIdComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const totalNumberOfRecordsToVirtualize = get(
|
||||
totalNumberOfRecordsToVirtualizeComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const realIndexByRecordIdMap = new Map<string, number>();
|
||||
|
||||
if (!isDefined(totalNumberOfRecordsToVirtualize)) {
|
||||
return { realIndexByRecordIdMap };
|
||||
}
|
||||
|
||||
for (
|
||||
let realIndex = 0;
|
||||
realIndex < totalNumberOfRecordsToVirtualize;
|
||||
realIndex++
|
||||
) {
|
||||
const recordId = get(
|
||||
recordIdByRealIndexComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey: { realIndex },
|
||||
}),
|
||||
);
|
||||
|
||||
if (isDefined(recordId)) {
|
||||
realIndexByRecordIdMap.set(recordId, realIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return { realIndexByRecordIdMap };
|
||||
},
|
||||
});
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { recordIdByRealIndexComponentState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentState';
|
||||
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type DefaultValue } from 'recoil';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const recordIdByRealIndexComponentFamilySelector =
|
||||
createComponentFamilySelector<Nullable<string>, Nullable<number>>({
|
||||
key: 'recordIdByRealIndexComponentFamilySelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId, familyKey }) =>
|
||||
({ get }) => {
|
||||
const realIndex = familyKey;
|
||||
|
||||
const recordIdByRealIndex = get(
|
||||
recordIdByRealIndexComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(realIndex)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const recordId = recordIdByRealIndex.get(realIndex);
|
||||
|
||||
return recordId;
|
||||
},
|
||||
set:
|
||||
({ familyKey, instanceId }) =>
|
||||
({ get, set }, newValue: Nullable<string> | DefaultValue) => {
|
||||
if (!isDefined(familyKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actualRecordIdByRealIndex = get(
|
||||
recordIdByRealIndexComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const newRecordIdByRealIndex = new Map(actualRecordIdByRealIndex);
|
||||
|
||||
if (isNonEmptyString(newValue)) {
|
||||
newRecordIdByRealIndex.set(familyKey, newValue);
|
||||
} else {
|
||||
newRecordIdByRealIndex.delete(familyKey);
|
||||
}
|
||||
|
||||
set(
|
||||
recordIdByRealIndexComponentState.atomFamily({ instanceId }),
|
||||
newRecordIdByRealIndex,
|
||||
);
|
||||
},
|
||||
});
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
|
||||
|
||||
export const recordIdByRealIndexComponentFamilyState =
|
||||
createComponentFamilyState<string | null, { realIndex: number | null }>({
|
||||
key: 'recordIdByRealIndexComponentFamilyState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: null,
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const recordIdByRealIndexComponentState = createComponentState<
|
||||
Map<number, string>
|
||||
>({
|
||||
key: 'recordIdByRealIndexComponentState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: new Map(),
|
||||
});
|
||||
Reference in New Issue
Block a user