From 86afc34e61c573b0a6a76277052229da8bc1631e Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 18 Apr 2024 10:44:00 +0200 Subject: [PATCH] Speed up RecordTableCell by 5x (#5023) Improved table cell performances by putting all hooks from RecordTableCell in RecordTableContext and RecordTable component, so that each cell now only subscribes to a reference to those hooks' returned function. We couldn't do memoization here since the problem is not to memoize between re-renders but to share the same function reference between hundreds of different components, so a context it the fastest way for this. I had to refactor the hooks a little bit so that they take as arguments what was previously taken from the cell's context. --- .../record-field/hooks/useInitDraftValueV2.ts | 57 ++++++++ .../record-table/components/RecordTable.tsx | 72 ++++++++++ ...=> RecordTableCellFieldContextWrapper.tsx} | 2 +- .../components/RecordTableRow.tsx | 4 +- .../contexts/RecordTableContext.ts | 19 +++ .../internal/useHandleContainerMouseEnter.ts | 68 ++++++++++ .../hooks/useRecordTableMoveFocus.ts | 19 +++ .../components/RecordTableCell.tsx | 67 ++++++---- .../components/RecordTableCellContainer.tsx | 118 ++++++++--------- .../components/RecordTableCellDisplayMode.tsx | 16 ++- .../RecordTableCellSoftFocusMode.tsx | 8 +- .../hooks/useCloseRecordTableCellV2.ts | 38 ++++++ .../hooks/useMoveSoftFocusToCellOnHoverV2.ts | 59 +++++++++ .../hooks/useOpenRecordTableCellFromCell.ts | 51 +++++++ .../hooks/useOpenRecordTableCellV2.ts | 125 ++++++++++++++++++ .../hooks/useSetSoftFocus.ts | 6 +- .../hooks/useTriggerContextMenu.ts | 46 +++++++ .../hooks/useUpsertRecordV2.ts | 60 +++++++++ .../record-table/types/MoveFocusDirection.ts | 1 + 19 files changed, 731 insertions(+), 105 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts rename packages/twenty-front/src/modules/object-record/record-table/components/{RecordTableCellContainer.tsx => RecordTableCellFieldContextWrapper.tsx} (97%) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useHandleContainerMouseEnter.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/types/MoveFocusDirection.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts new file mode 100644 index 000000000..3e84eccf7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useInitDraftValueV2.ts @@ -0,0 +1,57 @@ +import { isUndefined } from '@sniptt/guards'; +import { useRecoilCallback } from 'recoil'; + +import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; +import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; +import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { computeDraftValueFromFieldValue } from '@/object-record/record-field/utils/computeDraftValueFromFieldValue'; +import { computeDraftValueFromString } from '@/object-record/record-field/utils/computeDraftValueFromString'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector'; + +export const useInitDraftValueV2 = () => { + return useRecoilCallback( + ({ set, snapshot }) => + ({ + value, + entityId, + fieldDefinition, + }: { + value?: string; + entityId: string; + fieldDefinition: FieldDefinition; + }) => { + const recordFieldInputScopeId = `${entityId}-${fieldDefinition?.metadata?.fieldName}-scope`; + + const getDraftValueSelector = extractComponentSelector< + FieldInputDraftValue | undefined + >(recordFieldInputDraftValueComponentSelector, recordFieldInputScopeId); + + const recordFieldValue = snapshot + .getLoadable( + recordStoreFamilySelector({ + recordId: entityId, + fieldName: fieldDefinition.metadata.fieldName, + }), + ) + .getValue(); + + if (isUndefined(value)) { + set( + getDraftValueSelector(), + computeDraftValueFromFieldValue({ + fieldValue: recordFieldValue, + fieldDefinition, + }), + ); + } else { + set( + getDraftValueSelector(), + computeDraftValueFromString({ value, fieldDefinition }), + ); + } + }, + [], + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index aeeae2897..02d206f1e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -7,8 +7,20 @@ import { RecordTableBody } from '@/object-record/record-table/components/RecordT import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect'; import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader'; import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus'; +import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2'; +import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2'; +import { + OpenTableCellArgs, + useOpenRecordTableCellV2, +} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; +import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu'; +import { useUpsertRecordV2 } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2'; import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; +import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport'; import { RGBA } from '@/ui/theme/constants/Rgba'; import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState'; @@ -145,6 +157,59 @@ export const RecordTable = ({ objectNameSingular, }); + const { upsertRecord } = useUpsertRecordV2({ + objectNameSingular, + }); + + const handleUpsertRecord = ({ + persistField, + entityId, + fieldName, + }: { + persistField: () => void; + entityId: string; + fieldName: string; + }) => { + upsertRecord(persistField, entityId, fieldName, recordTableId); + }; + + const { openTableCell } = useOpenRecordTableCellV2(recordTableId); + + const handleOpenTableCell = (args: OpenTableCellArgs) => { + openTableCell(args); + }; + + const { moveFocus } = useRecordTableMoveFocus(recordTableId); + + const handleMoveFocus = (direction: MoveFocusDirection) => { + moveFocus(direction); + }; + + const { closeTableCell } = useCloseRecordTableCellV2(recordTableId); + + const handleCloseTableCell = () => { + closeTableCell(); + }; + + const { moveSoftFocusToCell } = + useMoveSoftFocusToCellOnHoverV2(recordTableId); + + const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => { + moveSoftFocusToCell(cellPosition); + }; + + const { triggerContextMenu } = useTriggerContextMenu({ + recordTableId, + }); + + const handleContextMenu = (event: React.MouseEvent, recordId: string) => { + triggerContextMenu(event, recordId); + }; + + const { handleContainerMouseEnter } = useHandleContainerMouseEnter({ + recordTableId, + }); + return ( { +export const RecordTableCellFieldContextWrapper = () => { const { objectMetadataItem } = useContext(RecordTableContext); const { columnDefinition } = useContext(RecordTableCellContext); const { recordId, pathToShowPage } = useContext(RecordTableRowContext); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx index 7b9dfc25b..20e27736a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx @@ -4,7 +4,7 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; -import { RecordTableCellContainer } from '@/object-record/record-table/components/RecordTableCellContainer'; +import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; @@ -67,7 +67,7 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => { }} key={column.fieldMetadataId} > - + ) : ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts index 80e2ad9a3..d3a9c7dbd 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts @@ -1,9 +1,28 @@ import { createContext } from 'react'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; +import { OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; +import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; type RecordTableContextProps = { objectMetadataItem: ObjectMetadataItem; + onUpsertRecord: ({ + persistField, + entityId, + fieldName, + }: { + persistField: () => void; + entityId: string; + fieldName: string; + }) => void; + onOpenTableCell: (args: OpenTableCellArgs) => void; + onMoveFocus: (direction: MoveFocusDirection) => void; + onCloseTableCell: () => void; + onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void; + onContextMenu: (event: React.MouseEvent, recordId: string) => void; + onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void; }; export const RecordTableContext = createContext( diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useHandleContainerMouseEnter.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useHandleContainerMouseEnter.ts new file mode 100644 index 000000000..2810c6f3b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useHandleContainerMouseEnter.ts @@ -0,0 +1,68 @@ +import { useRecoilCallback } from 'recoil'; + +import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2'; +import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState'; +import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState'; +import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; + +export type HandleContainerMouseEnterArgs = { + isHovered: boolean; + setIsHovered: React.Dispatch>; + cellPosition: TableCellPosition; +}; + +export const useHandleContainerMouseEnter = ({ + recordTableId, +}: { + recordTableId: string; +}) => { + const tableScopeId = getScopeIdFromComponentId(recordTableId); + + const { moveSoftFocusToCell } = + useMoveSoftFocusToCellOnHoverV2(recordTableId); + + const handleContainerMouseEnter = useRecoilCallback( + ({ snapshot, set }) => + ({ + isHovered, + setIsHovered, + cellPosition, + }: HandleContainerMouseEnterArgs) => { + const currentTableCellInEditModePositionState = extractComponentState( + currentTableCellInEditModePositionComponentState, + tableScopeId, + ); + + const currentTableCellInEditModePosition = getSnapshotValue( + snapshot, + currentTableCellInEditModePositionState, + ); + + const isTableCellInEditModeFamilyState = extractComponentFamilyState( + isTableCellInEditModeComponentFamilyState, + tableScopeId, + ); + + const isSomeCellInEditMode = getSnapshotValue( + snapshot, + isTableCellInEditModeFamilyState(currentTableCellInEditModePosition), + ); + + if (!isHovered && !isSomeCellInEditMode) { + setIsHovered(true); + moveSoftFocusToCell(cellPosition); + set(isSoftFocusUsingMouseState, true); + } + }, + [tableScopeId, moveSoftFocusToCell], + ); + + return { + handleContainerMouseEnter, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts index 2291659af..c5dffa386 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocus.ts @@ -1,6 +1,7 @@ import { useRecoilCallback } from 'recoil'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition'; @@ -167,6 +168,23 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => { ], ); + const moveFocus = (direction: MoveFocusDirection) => { + switch (direction) { + case 'up': + moveUp(); + break; + case 'down': + moveDown(); + break; + case 'left': + moveLeft(); + break; + case 'right': + moveRight(); + break; + } + }; + return { scopeId, moveDown, @@ -175,5 +193,6 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => { moveUp, setSoftFocusPosition, selectedRowIdsSelector, + moveFocus, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx index 945fd2e24..201043957 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx @@ -4,11 +4,9 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl import { FieldInput } from '@/object-record/record-field/components/FieldInput'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus'; import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer'; -import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell'; -import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; export const RecordTableCell = ({ @@ -16,55 +14,76 @@ export const RecordTableCell = ({ }: { customHotkeyScope: HotkeyScope; }) => { - const { closeTableCell } = useCloseRecordTableCell(); - const { upsertRecord } = useUpsertRecord(); - - const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus(); - + const { onUpsertRecord, onMoveFocus, onCloseTableCell } = + useContext(RecordTableContext); const { entityId, fieldDefinition } = useContext(FieldContext); const { isReadOnly } = useContext(RecordTableRowContext); const handleEnter: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); - moveDown(); + onCloseTableCell(); + onMoveFocus('down'); }; const handleSubmit: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); + onCloseTableCell(); }; const handleCancel = () => { - closeTableCell(); + onCloseTableCell(); }; const handleClickOutside: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); + onCloseTableCell(); }; const handleEscape: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); + onCloseTableCell(); }; const handleTab: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); - moveRight(); + onCloseTableCell(); + onMoveFocus('right'); }; const handleShiftTab: FieldInputEvent = (persistField) => { - upsertRecord(persistField); + onUpsertRecord({ + persistField, + entityId, + fieldName: fieldDefinition.metadata.fieldName, + }); - closeTableCell(); - moveLeft(); + onCloseTableCell(); + onMoveFocus('left'); }; return ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx index e1aeb4984..044bf8955 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx @@ -1,28 +1,26 @@ -import { ReactElement, useContext, useState } from 'react'; +import React, { ReactElement, useContext, useState } from 'react'; import styled from '@emotion/styled'; -import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { IconArrowUpRight } from 'twenty-ui'; import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon'; import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty'; import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; -import { useGetIsSomeCellInEditModeState } from '@/object-record/record-table/hooks/internal/useGetIsSomeCellInEditMode'; -import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; -import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; -import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState'; -import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; -import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; +import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition'; +import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; +import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext'; +import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState'; +import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; +import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId'; +import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode'; -import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell'; -import { useMoveSoftFocusToCurrentCellOnHover } from '../hooks/useMoveSoftFocusToCurrentCellOnHover'; -import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell'; import { RecordTableCellButton } from './RecordTableCellButton'; import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode'; @@ -64,65 +62,60 @@ export const RecordTableCellContainer = ({ nonEditModeContent, editHotkeyScope, }: RecordTableCellContainerProps) => { + const { columnIndex } = useContext(RecordTableCellContext); + const { isReadOnly, isSelected, recordId } = useContext( + RecordTableRowContext, + ); + const { onMoveSoftFocusToCell, onContextMenu, onCellMouseEnter } = + useContext(RecordTableContext); + + const cellPosition = useCurrentTableCellPosition(); + const [isHovered, setIsHovered] = useState(false); - const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); - const isSomeCellInEditModeState = useGetIsSomeCellInEditModeState(); + const { openTableCell } = useOpenRecordTableCellFromCell(); - const setIsSoftFocusUsingMouseState = useSetRecoilState( - isSoftFocusUsingMouseState, + const tableScopeId = useAvailableScopeIdOrThrow( + RecordTableScopeInternalContext, + getScopeIdOrUndefinedFromComponentId(), ); - const moveSoftFocusToCurrentCellOnHover = - useMoveSoftFocusToCurrentCellOnHover(); + const isTableCellInEditModeFamilyState = extractComponentFamilyState( + isTableCellInEditModeComponentFamilyState, + tableScopeId, + ); - const hasSoftFocus = useIsSoftFocusOnCurrentTableCell(); - const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell(); + const isSoftFocusOnTableCellFamilyState = extractComponentFamilyState( + isSoftFocusOnTableCellComponentFamilyState, + tableScopeId, + ); - const { openTableCell } = useOpenRecordTableCell(); + const isCurrentTableCellInEditMode = useRecoilValue( + isTableCellInEditModeFamilyState(cellPosition), + ); + + const hasSoftFocus = useRecoilValue( + isSoftFocusOnTableCellFamilyState(cellPosition), + ); + + const isEmpty = useIsFieldEmpty(); const handleButtonClick = () => { - setSoftFocusOnCurrentTableCell(); + onMoveSoftFocusToCell(cellPosition); openTableCell(); }; - const { isSelected, isReadOnly } = useContext(RecordTableRowContext); - - const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); - - const { setCurrentRowSelected } = useSetCurrentRowSelected(); - const handleContextMenu = (event: React.MouseEvent) => { - event.preventDefault(); - setCurrentRowSelected(true); - setContextMenuPosition({ - x: event.clientX, - y: event.clientY, - }); - setContextMenuOpenState(true); + onContextMenu(event, recordId); }; - const handleContainerMouseEnter = useRecoilCallback( - ({ snapshot }) => - () => { - const isSomeCellInEditMode = getSnapshotValue( - snapshot, - isSomeCellInEditModeState(), - ); - if (!isHovered && !isSomeCellInEditMode) { - setIsHovered(true); - moveSoftFocusToCurrentCellOnHover(); - setIsSoftFocusUsingMouseState(true); - } - }, - [ + const handleContainerMouseEnter = () => { + onCellMouseEnter({ + cellPosition, isHovered, - isSomeCellInEditModeState, - moveSoftFocusToCurrentCellOnHover, - setIsSoftFocusUsingMouseState, - ], - ); + setIsHovered, + }); + }; const handleContainerMouseLeave = () => { setIsHovered(false); @@ -130,9 +123,6 @@ export const RecordTableCellContainer = ({ const editModeContentOnly = useIsFieldInputOnly(); - const isEmpty = useIsFieldEmpty(); - - const { columnIndex } = useContext(RecordTableCellContext); const isFirstColumn = columnIndex === 0; const customButtonIcon = useGetButtonIcon(); const buttonIcon = isFirstColumn ? IconArrowUpRight : customButtonIcon; @@ -148,7 +138,7 @@ export const RecordTableCellContainer = ({ return ( handleContextMenu(event)} + onContextMenu={handleContextMenu} isInEditMode={isCurrentTableCellInEditMode} > )} - - {editModeContentOnly ? editModeContent : nonEditModeContent} - + {!isEmpty && ( + + {editModeContentOnly ? editModeContent : nonEditModeContent} + + )} )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode.tsx index d69c4d488..9a052ee96 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode.tsx @@ -1,21 +1,23 @@ -import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; -import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; +import { useContext } from 'react'; -import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell'; +import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition'; +import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer'; export const RecordTableCellDisplayMode = ({ children, }: React.PropsWithChildren) => { - const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentTableCell(); + const cellPosition = useCurrentTableCellPosition(); + const { onMoveSoftFocusToCell } = useContext(RecordTableContext); + const { openTableCell } = useOpenRecordTableCellFromCell(); const isFieldInputOnly = useIsFieldInputOnly(); - const { openTableCell } = useOpenRecordTableCell(); - const handleClick = () => { - setSoftFocusOnCurrentCell(); + onMoveSoftFocusToCell(cellPosition); if (!isFieldInputOnly) { openTableCell(); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx index 7f10d98ba..ecec3005c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode.tsx @@ -6,7 +6,7 @@ import { useClearField } from '@/object-record/record-field/hooks/useClearField' import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFieldClearable'; import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; -import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell'; +import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell'; import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey'; @@ -20,7 +20,7 @@ type RecordTableCellSoftFocusModeProps = PropsWithChildren; export const RecordTableCellSoftFocusMode = ({ children, }: RecordTableCellSoftFocusModeProps) => { - const { openTableCell } = useOpenRecordTableCell(); + const { openTableCell } = useOpenRecordTableCellFromCell(); const isFieldInputOnly = useIsFieldInputOnly(); @@ -82,9 +82,7 @@ export const RecordTableCellSoftFocusMode = ({ keyboardEvent.stopPropagation(); keyboardEvent.stopImmediatePropagation(); - openTableCell({ - initialValue: keyboardEvent.key, - }); + openTableCell(keyboardEvent.key); } }, TableHotkeyScope.TableSoftFocus, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2.ts new file mode 100644 index 000000000..3f1277212 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2.ts @@ -0,0 +1,38 @@ +import { useResetRecoilState } from 'recoil'; + +import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; + +import { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode'; +import { TableHotkeyScope } from '../../types/TableHotkeyScope'; + +export const useCloseRecordTableCellV2 = (recordTableScopeId: string) => { + const setHotkeyScope = useSetHotkeyScope(); + const { setDragSelectionStartEnabled } = useDragSelect(); + const { pendingRecordIdState } = useRecordTableStates(recordTableScopeId); + + const { toggleClickOutsideListener } = useClickOutsideListener( + SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID, + ); + + const closeCurrentTableCellInEditMode = + useCloseCurrentTableCellInEditMode(recordTableScopeId); + + const resetRecordTablePendingRecordId = + useResetRecoilState(pendingRecordIdState); + + const closeTableCell = async () => { + toggleClickOutsideListener(true); + setDragSelectionStartEnabled(true); + closeCurrentTableCellInEditMode(); + setHotkeyScope(TableHotkeyScope.TableSoftFocus); + resetRecordTablePendingRecordId(); + }; + + return { + closeTableCell, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2.ts new file mode 100644 index 000000000..171f209bb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2.ts @@ -0,0 +1,59 @@ +import { useRecoilCallback } from 'recoil'; + +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { useSetSoftFocus } from '@/object-record/record-table/record-table-cell/hooks/useSetSoftFocus'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; +import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; + +import { TableHotkeyScope } from '../../types/TableHotkeyScope'; + +export const useMoveSoftFocusToCellOnHoverV2 = (recordTableId: string) => { + const setSoftFocus = useSetSoftFocus(recordTableId); + + const { + currentTableCellInEditModePositionState, + isTableCellInEditModeFamilyState, + } = useRecordTableStates(recordTableId); + + const moveSoftFocusToCell = useRecoilCallback( + ({ snapshot }) => + (cellPosition: TableCellPosition) => { + const currentTableCellInEditModePosition = getSnapshotValue( + snapshot, + currentTableCellInEditModePositionState, + ); + + const isSomeCellInEditMode = snapshot + .getLoadable( + isTableCellInEditModeFamilyState( + currentTableCellInEditModePosition, + ), + ) + .getValue(); + + const currentHotkeyScope = snapshot + .getLoadable(currentHotkeyScopeState) + .getValue(); + + if ( + currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus && + currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode && + currentHotkeyScope.scope !== TableHotkeyScope.Table + ) { + return; + } + + if (!isSomeCellInEditMode) { + setSoftFocus(cellPosition); + } + }, + [ + currentTableCellInEditModePositionState, + isTableCellInEditModeFamilyState, + setSoftFocus, + ], + ); + + return { moveSoftFocusToCell }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell.ts new file mode 100644 index 000000000..cf7846adf --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell.ts @@ -0,0 +1,51 @@ +import { useContext } from 'react'; + +import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; +import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; +import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; +import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; + +import { TableHotkeyScope } from '../../types/TableHotkeyScope'; + +export const DEFAULT_CELL_SCOPE: HotkeyScope = { + scope: TableHotkeyScope.CellEditMode, +}; + +export type OpenTableCellArgs = { + initialValue?: string; + cellPosition: TableCellPosition; + isReadOnly: boolean; + pathToShowPage: string; + customCellHotkeyScope: HotkeyScope | null; + fieldDefinition: FieldDefinition; + entityId: string; +}; + +export const useOpenRecordTableCellFromCell = () => { + const { onOpenTableCell } = useContext(RecordTableContext); + const cellPosition = useCurrentTableCellPosition(); + const customCellHotkeyScope = useContext(CellHotkeyScopeContext); + const { entityId, fieldDefinition } = useContext(FieldContext); + const { isReadOnly, pathToShowPage } = useContext(RecordTableRowContext); + + const openTableCell = (initialValue?: string) => { + onOpenTableCell({ + cellPosition, + customCellHotkeyScope, + entityId, + fieldDefinition, + isReadOnly, + pathToShowPage, + initialValue, + }); + }; + + return { + openTableCell, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts new file mode 100644 index 000000000..e9508f536 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2.ts @@ -0,0 +1,125 @@ +import { useNavigate } from 'react-router-dom'; +import { useRecoilCallback } from 'recoil'; + +import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2'; +import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId'; +import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus'; +import { useMoveEditModeToTableCellPosition } from '@/object-record/record-table/hooks/internal/useMoveEditModeToCellPosition'; +import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; +import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { isDefined } from '~/utils/isDefined'; + +import { TableHotkeyScope } from '../../types/TableHotkeyScope'; + +export const DEFAULT_CELL_SCOPE: HotkeyScope = { + scope: TableHotkeyScope.CellEditMode, +}; + +export type OpenTableCellArgs = { + initialValue?: string; + cellPosition: TableCellPosition; + isReadOnly: boolean; + pathToShowPage: string; + customCellHotkeyScope: HotkeyScope | null; + fieldDefinition: FieldDefinition; + entityId: string; +}; + +export const useOpenRecordTableCellV2 = (tableScopeId: string) => { + const moveEditModeToTableCellPosition = + useMoveEditModeToTableCellPosition(tableScopeId); + + const setHotkeyScope = useSetHotkeyScope(); + const { setDragSelectionStartEnabled } = useDragSelect(); + + const navigate = useNavigate(); + const leaveTableFocus = useLeaveTableFocus(tableScopeId); + const { toggleClickOutsideListener } = useClickOutsideListener( + SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID, + ); + + const initDraftValue = useInitDraftValueV2(); + + const openTableCell = useRecoilCallback( + ({ snapshot }) => + ({ + initialValue, + cellPosition, + isReadOnly, + pathToShowPage, + customCellHotkeyScope, + fieldDefinition, + entityId, + }: OpenTableCellArgs) => { + if (isReadOnly) { + return; + } + + const isFirstColumnCell = cellPosition.column === 0; + + const fieldValue = getSnapshotValue( + snapshot, + recordStoreFamilySelector({ + recordId: entityId, + fieldName: fieldDefinition.metadata.fieldName, + }), + ); + + const isEmpty = isFieldValueEmpty({ + fieldDefinition, + fieldValue, + }); + + if (isFirstColumnCell && !isEmpty) { + leaveTableFocus(); + navigate(pathToShowPage); + return; + } + + setDragSelectionStartEnabled(false); + + moveEditModeToTableCellPosition(cellPosition); + + initDraftValue({ + value: initialValue, + entityId, + fieldDefinition, + }); + + toggleClickOutsideListener(false); + + if (isDefined(customCellHotkeyScope)) { + setHotkeyScope( + customCellHotkeyScope.scope, + customCellHotkeyScope.customScopes, + ); + } else { + setHotkeyScope( + DEFAULT_CELL_SCOPE.scope, + DEFAULT_CELL_SCOPE.customScopes, + ); + } + }, + [ + setDragSelectionStartEnabled, + toggleClickOutsideListener, + leaveTableFocus, + navigate, + setHotkeyScope, + initDraftValue, + moveEditModeToTableCellPosition, + ], + ); + + return { + openTableCell, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetSoftFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetSoftFocus.ts index 9b630f4e2..1d8a1dc54 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetSoftFocus.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useSetSoftFocus.ts @@ -7,10 +7,10 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -export const useSetSoftFocus = () => { - const setSoftFocusPosition = useSetSoftFocusPosition(); +export const useSetSoftFocus = (recordTableId?: string) => { + const setSoftFocusPosition = useSetSoftFocusPosition(recordTableId); - const { isSoftFocusActiveState } = useRecordTableStates(); + const { isSoftFocusActiveState } = useRecordTableStates(recordTableId); const setHotkeyScope = useSetHotkeyScope(); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts new file mode 100644 index 000000000..b9c04a8b7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts @@ -0,0 +1,46 @@ +import { useRecoilCallback } from 'recoil'; + +import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; +import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; +import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState'; + +export const useTriggerContextMenu = ({ + recordTableId, +}: { + recordTableId: string; +}) => { + const triggerContextMenu = useRecoilCallback( + ({ set, snapshot }) => + (event: React.MouseEvent, recordId: string) => { + event.preventDefault(); + + const tableScopeId = getScopeIdFromComponentId(recordTableId); + + set(contextMenuPositionState, { + x: event.clientX, + y: event.clientY, + }); + set(contextMenuIsOpenState, true); + + const isRowSelectedFamilyState = extractComponentFamilyState( + isRowSelectedComponentFamilyState, + tableScopeId, + ); + + const isRowSelected = getSnapshotValue( + snapshot, + isRowSelectedFamilyState(recordId), + ); + + if (isRowSelected !== true) { + set(isRowSelectedFamilyState(recordId), true); + } + }, + [recordTableId], + ); + + return { triggerContextMenu }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2.ts new file mode 100644 index 000000000..16a8d8ead --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2.ts @@ -0,0 +1,60 @@ +import { useRecoilCallback } from 'recoil'; + +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; +import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; +import { isDefined } from '~/utils/isDefined'; + +export const useUpsertRecordV2 = ({ + objectNameSingular, +}: { + objectNameSingular: string; +}) => { + const { createOneRecord } = useCreateOneRecord({ + objectNameSingular, + }); + + const upsertRecord = useRecoilCallback( + ({ snapshot }) => + ( + persistField: () => void, + entityId: string, + fieldName: string, + tableScopeId: string, + ) => { + const recordTablePendingRecordIdState = extractComponentState( + recordTablePendingRecordIdComponentState, + tableScopeId, + ); + + const recordTablePendingRecordId = getSnapshotValue( + snapshot, + recordTablePendingRecordIdState, + ); + const fieldScopeId = `${entityId}-${fieldName}`; + + const draftValueSelector = extractComponentSelector( + recordFieldInputDraftValueComponentSelector, + fieldScopeId, + ); + + const draftValue = getSnapshotValue(snapshot, draftValueSelector()); + + if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) { + createOneRecord({ + id: recordTablePendingRecordId, + name: draftValue, + position: 'first', + }); + } else if (!recordTablePendingRecordId) { + persistField(); + } + }, + [createOneRecord], + ); + + return { upsertRecord }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/types/MoveFocusDirection.ts b/packages/twenty-front/src/modules/object-record/record-table/types/MoveFocusDirection.ts new file mode 100644 index 000000000..9dfb48007 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/types/MoveFocusDirection.ts @@ -0,0 +1 @@ +export type MoveFocusDirection = 'up' | 'down' | 'left' | 'right';