Improve RecordTableCellperformances (#3659)

* Improve RecordTableCellperformances

* Fixes
This commit is contained in:
Charles Bochet
2024-01-28 20:32:28 +01:00
committed by GitHub
parent ada8f55574
commit 419f8adde6
46 changed files with 667 additions and 637 deletions

View File

@ -28,8 +28,9 @@ export const triggerDeleteRecordsOptimisticEffect = ({
objectMetadataItem.nameSingular,
cachedConnection,
)
)
) {
return cachedConnection;
}
const { variables } =
parseApolloStoreFieldName<CachedObjectRecordQueryVariables>(

View File

@ -0,0 +1,46 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDefined } from '~/utils/isDefined';
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
export const useObjectMetadataItemOnly = ({
objectNameSingular,
}: ObjectMetadataItemIdentifier) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const mockObjectMetadataItems = getObjectMetadataItemsMock();
let objectMetadataItem = useRecoilValue(
objectMetadataItemFamilySelector({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
);
let objectMetadataItems = useRecoilValue(objectMetadataItemsState);
if (!currentWorkspace) {
objectMetadataItem =
mockObjectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === objectNameSingular,
) ?? null;
objectMetadataItems = mockObjectMetadataItems;
}
if (!isDefined(objectMetadataItem)) {
throw new ObjectMetadataItemNotFoundError(
objectNameSingular,
objectMetadataItems,
);
}
return {
objectMetadataItem,
};
};

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { EntityChip } from '@/ui/display/chip/components/EntityChip';
@ -15,10 +16,14 @@ export const RecordChip = ({
record,
maxWidth,
}: RecordChipProps) => {
const { mapToObjectRecordIdentifier } = useObjectMetadataItem({
const { objectMetadataItem } = useObjectMetadataItemOnly({
objectNameSingular,
});
const mapToObjectRecordIdentifier = useMapToObjectRecordIdentifier({
objectMetadataItem,
});
const objectRecordIdentifier = mapToObjectRecordIdentifier(record);
return (

View File

@ -4,13 +4,13 @@ import { expect } from '@storybook/test';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
const recordTableId = 'people';
const objectNamePlural = 'people';
const objectNameSingular = 'person';
const onColumnsChange = jest.fn();
const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => {
@ -37,7 +37,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => {
describe('useObjectRecordTable', () => {
it('should skip fetch if currentWorkspace is undefined', async () => {
const { result } = renderHook(
() => useObjectRecordTable(objectNamePlural),
() => useLoadRecordIndexTable(objectNameSingular),
{
wrapper: Wrapper,
},

View File

@ -1,4 +1,3 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext';
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
@ -18,10 +17,6 @@ export const RecordIndexTableContainer = ({
objectNameSingular,
createRecord,
}: RecordIndexTableContainerProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
});
@ -37,7 +32,7 @@ export const RecordIndexTableContainer = ({
<>
<RecordTableWithWrappers
recordTableId={recordTableId}
objectNamePlural={objectMetadataItem.namePlural}
objectNameSingular={objectNameSingular}
viewBarId={viewBarId}
updateRecordMutation={updateEntity}
createRecord={createRecord}

View File

@ -18,17 +18,11 @@ export const RecordIndexTableContainerEffect = ({
recordTableId,
viewBarId,
}: RecordIndexTableContainerEffectProps) => {
const {
setAvailableTableColumns,
setOnEntityCountChange,
setObjectMetadataConfig,
} = useRecordTable({ recordTableId });
const { setAvailableTableColumns, setOnEntityCountChange } = useRecordTable({
recordTableId,
});
const {
objectMetadataItem,
basePathToShowPage,
labelIdentifierFieldMetadata,
} = useObjectMetadataItem({
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
@ -39,20 +33,6 @@ export const RecordIndexTableContainerEffect = ({
viewBarId,
});
useEffect(() => {
if (basePathToShowPage && labelIdentifierFieldMetadata) {
setObjectMetadataConfig?.({
basePathToShowPage,
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
});
}
}, [
basePathToShowPage,
objectMetadataItem,
labelIdentifierFieldMetadata,
setObjectMetadataConfig,
]);
useEffect(() => {
const availableTableColumns = columnDefinitions.filter(
filterAvailableTableColumns,

View File

@ -2,30 +2,23 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { signInBackgroundMockCompanies } from '@/sign-in-background-mock/constants/signInBackgroundMockCompanies';
import { useFindManyRecords } from './useFindManyRecords';
import { useFindManyRecords } from '../../hooks/useFindManyRecords';
export const useObjectRecordTable = (objectNamePlural: string) => {
export const useLoadRecordIndexTable = (objectNameSingular: string) => {
const { setRecordTableData, setIsRecordTableInitialLoading } =
useRecordTable();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{
objectNameSingular,
},
);
const {
getTableFiltersState,
getTableSortsState,
@ -38,12 +31,12 @@ export const useObjectRecordTable = (objectNamePlural: string) => {
const requestFilters = turnObjectDropdownFilterIntoQueryFilter(
tableFilters,
foundObjectMetadataItem?.fields ?? [],
objectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
tableSorts,
foundObjectMetadataItem?.fields ?? [],
objectMetadataItem?.fields ?? [],
);
const { records, loading, fetchMoreRecords, queryStateIdentifier } =

View File

@ -1,12 +1,13 @@
import { useCallback } from 'react';
import { useCallback, useContext } from 'react';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
import { Checkbox } from '@/ui/input/components/Checkbox';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSelected';
const StyledContainer = styled.div`
align-items: center;
cursor: pointer;
@ -18,8 +19,11 @@ const StyledContainer = styled.div`
`;
export const CheckboxCell = () => {
const { recordId } = useContext(RecordTableRowContext);
const { isRowSelectedFamilyState } = useRecordTableStates();
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
const { currentRowSelected, setCurrentRowSelected } = useCurrentRowSelected();
const { setCurrentRowSelected } = useSetCurrentRowSelected();
const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId));
const handleClick = useCallback(() => {
setCurrentRowSelected(!currentRowSelected);

View File

@ -1,10 +1,12 @@
import { useContext } from 'react';
import { useRef } from 'react';
import styled from '@emotion/styled';
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
import { RecordTableBody } from '@/object-record/record-table/components/RecordTableBody';
import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect';
import { RecordTableFirstColumnScrollEffect } from '@/object-record/record-table/components/RecordTableFirstColumnScrollObserver';
import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader';
import { RecordTableRefContext } from '@/object-record/record-table/contexts/RecordTableRefContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
import { rgba } from '@/ui/theme/constants/colors';
@ -48,8 +50,7 @@ const StyledTable = styled.table`
}
}
th,
td {
th {
background-color: ${({ theme }) => theme.background.primary};
border-right: 1px solid ${({ theme }) => theme.border.color.light};
}
@ -98,31 +99,43 @@ const StyledTable = styled.table`
type RecordTableProps = {
recordTableId: string;
objectNamePlural: string;
objectNameSingular: string;
onColumnsChange: (columns: any) => void;
createRecord: () => void;
};
export const RecordTable = ({
recordTableId,
objectNamePlural,
objectNameSingular,
onColumnsChange,
createRecord,
}: RecordTableProps) => {
const recordTableRef = useContext(RecordTableRefContext);
const recordTableRef = useRef<HTMLTableElement>(null);
const { scopeId } = useRecordTableStates(recordTableId);
const { objectMetadataItem } = useObjectMetadataItemOnly({
objectNameSingular,
});
return (
<RecordTableScope
recordTableScopeId={scopeId}
onColumnsChange={onColumnsChange}
>
{!!objectNamePlural && (
<StyledTable ref={recordTableRef} className="entity-table-cell">
<RecordTableHeader createRecord={createRecord} />
<RecordTableBodyEffect objectNamePlural={objectNamePlural} />
<RecordTableBody objectNamePlural={objectNamePlural} />
</StyledTable>
{!!objectNameSingular && (
<RecordTableContext.Provider
value={{
objectMetadataItem,
recordTableRef,
}}
>
<RecordTableFirstColumnScrollEffect />
<StyledTable ref={recordTableRef} className="entity-table-cell">
<RecordTableHeader createRecord={createRecord} />
<RecordTableBodyEffect objectNameSingular={objectNameSingular} />
<RecordTableBody objectNameSingular={objectNameSingular} />
</StyledTable>
</RecordTableContext.Provider>
)}
</RecordTableScope>
);

View File

@ -2,15 +2,15 @@ import { useRecoilValue } from 'recoil';
import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader';
import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
import { RowIdContext } from '@/object-record/record-table/contexts/RowIdContext';
import { RowIndexContext } from '@/object-record/record-table/contexts/RowIndexContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
type RecordTableBodyProps = {
objectNamePlural: string;
objectNameSingular: string;
};
export const RecordTableBody = ({ objectNamePlural }: RecordTableBodyProps) => {
export const RecordTableBody = ({
objectNameSingular,
}: RecordTableBodyProps) => {
const { getTableRowIdsState } = useRecordTableStates();
const tableRowIds = useRecoilValue(getTableRowIdsState());
@ -18,15 +18,15 @@ export const RecordTableBody = ({ objectNamePlural }: RecordTableBodyProps) => {
return (
<>
<tbody>
{tableRowIds.map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RecordTableRow key={rowId} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
{tableRowIds.map((recordId, rowIndex) => (
<RecordTableRow
key={recordId}
recordId={recordId}
rowIndex={rowIndex}
/>
))}
</tbody>
<RecordTableBodyFetchMoreLoader objectNamePlural={objectNamePlural} />
<RecordTableBodyFetchMoreLoader objectNameSingular={objectNameSingular} />
</>
);
};

View File

@ -1,16 +1,16 @@
import { useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
type RecordTableBodyEffectProps = {
objectNamePlural: string;
objectNameSingular: string;
};
export const RecordTableBodyEffect = ({
objectNamePlural,
objectNameSingular,
}: RecordTableBodyEffectProps) => {
const {
fetchMoreRecords: fetchMoreObjects,
@ -18,7 +18,7 @@ export const RecordTableBodyEffect = ({
setRecordTableData,
queryStateIdentifier,
loading,
} = useObjectRecordTable(objectNamePlural);
} = useLoadRecordIndexTable(objectNameSingular);
const { getTableLastRowVisibleState } = useRecordTableStates();

View File

@ -1,18 +1,18 @@
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMoreLoader';
type RecordTableBodyFetchMoreLoaderProps = {
objectNamePlural: string;
objectNameSingular: string;
};
export const RecordTableBodyFetchMoreLoader = ({
objectNamePlural,
objectNameSingular,
}: RecordTableBodyFetchMoreLoaderProps) => {
const { queryStateIdentifier } = useObjectRecordTable(objectNamePlural);
const { queryStateIdentifier } = useLoadRecordIndexTable(objectNameSingular);
const { setRecordTableLastRowVisible } = useRecordTable();
const isFetchingMoreObjects = useRecoilValue(

View File

@ -1,32 +1,31 @@
import { useContext } from 'react';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
import { ColumnContext } from '@/object-record/record-table/contexts/ColumnContext';
import { ColumnIndexContext } from '@/object-record/record-table/contexts/ColumnIndexContext';
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
import { RowIdContext } from '@/object-record/record-table/contexts/RowIdContext';
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 { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
import { useCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useCurrentRowSelected';
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
export const RecordTableCellContainer = ({
cellIndex,
}: {
cellIndex: number;
}) => {
const StyledContainer = styled.td<{ isSelected: boolean }>`
background: ${({ isSelected, theme }) =>
isSelected ? theme.accent.quaternary : theme.background.primary};
`;
export const RecordTableCellContainer = () => {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const currentRowId = useContext(RowIdContext);
const { setCurrentRowSelected } = useCurrentRowSelected();
const { setCurrentRowSelected } = useSetCurrentRowSelected();
const handleContextMenu = (event: React.MouseEvent) => {
event.preventDefault();
@ -38,16 +37,15 @@ export const RecordTableCellContainer = ({
setContextMenuOpenState(true);
};
const columnDefinition = useContext(ColumnContext);
const { basePathToShowPage, objectMetadataItem } = useObjectMetadataItem({
objectNameSingular:
columnDefinition?.metadata.objectMetadataNameSingular || '',
});
const { objectMetadataItem } = useContext(RecordTableContext);
const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage, isSelected } = useContext(
RecordTableRowContext,
);
const updateRecord = useContext(RecordUpdateContext);
if (!columnDefinition || !currentRowId) {
if (!columnDefinition) {
return null;
}
@ -56,30 +54,29 @@ export const RecordTableCellContainer = ({
: TableHotkeyScope.CellEditMode;
return (
<RecoilScope>
<ColumnIndexContext.Provider value={cellIndex}>
<td onContextMenu={(event) => handleContextMenu(event)}>
<FieldContext.Provider
value={{
recoilScopeId: currentRowId + columnDefinition.label,
entityId: currentRowId,
fieldDefinition: columnDefinition,
useUpdateRecord: () => [updateRecord, {}],
hotkeyScope: customHotkeyScope,
basePathToShowPage,
isLabelIdentifier: isLabelIdentifierField({
fieldMetadataItem: {
id: columnDefinition.fieldMetadataId,
name: columnDefinition.metadata.fieldName,
},
objectMetadataItem,
}),
}}
>
<RecordTableCell customHotkeyScope={{ scope: customHotkeyScope }} />
</FieldContext.Provider>
</td>
</ColumnIndexContext.Provider>
</RecoilScope>
<StyledContainer
isSelected={isSelected}
onContextMenu={(event) => handleContextMenu(event)}
>
<FieldContext.Provider
value={{
recoilScopeId: recordId + columnDefinition.label,
entityId: recordId,
fieldDefinition: columnDefinition,
useUpdateRecord: () => [updateRecord, {}],
hotkeyScope: customHotkeyScope,
basePathToShowPage: pathToShowPage,
isLabelIdentifier: isLabelIdentifierField({
fieldMetadataItem: {
id: columnDefinition.fieldMetadataId,
name: columnDefinition.metadata.fieldName,
},
objectMetadataItem,
}),
}}
>
<RecordTableCell customHotkeyScope={{ scope: customHotkeyScope }} />
</FieldContext.Provider>
</StyledContainer>
);
};

View File

@ -1,11 +1,11 @@
import { useContext, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { RecordTableRefContext } from '@/object-record/record-table/contexts/RecordTableRefContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
export const RecordTableFirstColumnScrollEffect = () => {
const recordTableRef = useContext(RecordTableRefContext);
const { recordTableRef } = useContext(RecordTableContext);
const scrollLeft = useRecoilValue(scrollLeftState);

View File

@ -1,19 +0,0 @@
import { useRef } from 'react';
import { RecordTableRefContext } from '@/object-record/record-table/contexts/RecordTableRefContext';
export type RecordTableRefContextWrapperProps = {
children: React.ReactNode;
};
export const RecordTableRefContextWrapper = ({
children,
}: RecordTableRefContextWrapperProps) => {
const tableRef = useRef<HTMLTableElement>(null);
return (
<RecordTableRefContext.Provider value={tableRef}>
{children}
</RecordTableRefContext.Provider>
);
};

View File

@ -3,35 +3,33 @@ import { useInView } from 'react-intersection-observer';
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 { 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 { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { ColumnContext } from '../contexts/ColumnContext';
import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSelected';
import { CheckboxCell } from './CheckboxCell';
export const StyledRow = styled.tr<{ selected: boolean }>`
background: ${(props) =>
props.selected ? props.theme.accent.quaternary : 'none'};
`;
type RecordTableRowProps = {
rowId: string;
recordId: string;
rowIndex: number;
};
const StyledPlaceholder = styled.td`
height: 30px;
`;
export const RecordTableRow = ({ rowId }: RecordTableRowProps) => {
const { getVisibleTableColumnsSelector } = useRecordTableStates();
export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
const { getVisibleTableColumnsSelector, isRowSelectedFamilyState } =
useRecordTableStates();
const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId));
const { objectMetadataItem } = useContext(RecordTableContext);
const visibleTableColumns = useRecoilValue(getVisibleTableColumnsSelector());
const { currentRowSelected } = useCurrentRowSelected();
const scrollWrapperRef = useContext(ScrollWrapperContext);
const { ref: elementRef, inView } = useInView({
@ -40,34 +38,46 @@ export const RecordTableRow = ({ rowId }: RecordTableRowProps) => {
});
return (
<StyledRow
ref={elementRef}
data-testid={`row-id-${rowId}`}
selected={currentRowSelected}
data-selectable-id={rowId}
<RecordTableRowContext.Provider
value={{
recordId,
rowIndex,
pathToShowPage:
getBasePathToShowPage({ objectMetadataItem }) + recordId,
isSelected: currentRowSelected,
}}
>
{inView ? (
<>
<td>
<CheckboxCell />
</td>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column, columnIndex) => {
return (
<ColumnContext.Provider
value={column}
key={column.fieldMetadataId}
>
<RecordTableCellContainer cellIndex={columnIndex} />
</ColumnContext.Provider>
);
})}
<td></td>
</>
) : (
<StyledPlaceholder />
)}
</StyledRow>
<tr
ref={elementRef}
data-testid={`row-id-${recordId}`}
data-selectable-id={recordId}
>
{inView ? (
<>
<td>
<CheckboxCell />
</td>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column, columnIndex) => {
return (
<RecordTableCellContext.Provider
value={{
columnDefinition: column,
columnIndex,
}}
key={column.fieldMetadataId}
>
<RecordTableCellContainer />
</RecordTableCellContext.Provider>
);
})}
<td></td>
</>
) : (
<StyledPlaceholder />
)}
</tr>
</RecordTableRowContext.Provider>
);
};

View File

@ -3,11 +3,8 @@ import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
import { RecordTableFirstColumnScrollEffect } from '@/object-record/record-table/components/RecordTableFirstColumnScrollObserver';
import { RecordTableRefContextWrapper } from '@/object-record/record-table/components/RecordTableRefContext';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { IconPlus } from '@/ui/display/icon';
@ -66,7 +63,7 @@ const StyledTableContainer = styled.div`
`;
type RecordTableWithWrappersProps = {
objectNamePlural: string;
objectNameSingular: string;
recordTableId: string;
viewBarId: string;
updateRecordMutation: (params: any) => void;
@ -76,7 +73,7 @@ type RecordTableWithWrappersProps = {
export const RecordTableWithWrappers = ({
updateRecordMutation,
createRecord,
objectNamePlural,
objectNameSingular,
recordTableId,
viewBarId,
}: RecordTableWithWrappersProps) => {
@ -95,10 +92,6 @@ export const RecordTableWithWrappers = ({
recordTableId,
});
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{
objectNameSingular,
@ -112,52 +105,49 @@ export const RecordTableWithWrappers = ({
return (
<EntityDeleteContext.Provider value={deleteOneRecord}>
<ScrollWrapper>
<RecordTableRefContextWrapper>
<RecordTableFirstColumnScrollEffect />
<RecordUpdateContext.Provider value={updateRecordMutation}>
<StyledTableWithHeader>
<StyledTableContainer>
<div ref={tableBodyRef}>
<RecordTable
recordTableId={recordTableId}
objectNamePlural={objectNamePlural}
onColumnsChange={useRecoilCallback(() => (columns) => {
persistViewFields(
mapColumnDefinitionsToViewFields(columns),
);
})}
createRecord={createRecord}
/>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={resetTableRowSelection}
onDragSelectionChange={setRowSelectedState}
/>
</div>
<RecordTableInternalEffect
<RecordUpdateContext.Provider value={updateRecordMutation}>
<StyledTableWithHeader>
<StyledTableContainer>
<div ref={tableBodyRef}>
<RecordTable
recordTableId={recordTableId}
tableBodyRef={tableBodyRef}
objectNameSingular={objectNameSingular}
onColumnsChange={useRecoilCallback(() => (columns) => {
persistViewFields(
mapColumnDefinitionsToViewFields(columns),
);
})}
createRecord={createRecord}
/>
{!isRecordTableInitialLoading && numberOfTableRows === 0 && (
<StyledObjectEmptyContainer>
<StyledEmptyObjectTitle>
No {foundObjectMetadataItem?.namePlural}
</StyledEmptyObjectTitle>
<StyledEmptyObjectSubTitle>
Create one:
</StyledEmptyObjectSubTitle>
<Button
Icon={IconPlus}
title={`Add a ${foundObjectMetadataItem?.nameSingular}`}
variant={'secondary'}
onClick={createRecord}
/>
</StyledObjectEmptyContainer>
)}
</StyledTableContainer>
</StyledTableWithHeader>
</RecordUpdateContext.Provider>
</RecordTableRefContextWrapper>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={resetTableRowSelection}
onDragSelectionChange={setRowSelectedState}
/>
</div>
<RecordTableInternalEffect
recordTableId={recordTableId}
tableBodyRef={tableBodyRef}
/>
{!isRecordTableInitialLoading && numberOfTableRows === 0 && (
<StyledObjectEmptyContainer>
<StyledEmptyObjectTitle>
No {foundObjectMetadataItem?.namePlural}
</StyledEmptyObjectTitle>
<StyledEmptyObjectSubTitle>
Create one:
</StyledEmptyObjectSubTitle>
<Button
Icon={IconPlus}
title={`Add a ${foundObjectMetadataItem?.nameSingular}`}
variant={'secondary'}
onClick={createRecord}
/>
</StyledObjectEmptyContainer>
)}
</StyledTableContainer>
</StyledTableWithHeader>
</RecordUpdateContext.Provider>
</ScrollWrapper>
</EntityDeleteContext.Provider>
);

View File

@ -1,8 +0,0 @@
import { createContext } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '../types/ColumnDefinition';
export const ColumnContext =
createContext<ColumnDefinition<FieldMetadata> | null>(null);

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const ColumnIndexContext = createContext<number>(0);

View File

@ -0,0 +1,13 @@
import { createContext } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
type RecordTableRowContextProps = {
columnDefinition: ColumnDefinition<FieldMetadata>;
columnIndex: number;
};
export const RecordTableCellContext = createContext<RecordTableRowContextProps>(
{} as RecordTableRowContextProps,
);

View File

@ -0,0 +1,12 @@
import { createContext } from 'react';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
type RecordTableContextProps = {
objectMetadataItem: ObjectMetadataItem;
recordTableRef: React.RefObject<HTMLDivElement>;
};
export const RecordTableContext = createContext<RecordTableContextProps>(
{} as RecordTableContextProps,
);

View File

@ -1,7 +0,0 @@
import { createContext, RefObject } from 'react';
export const RecordTableRefContext = createContext<RefObject<HTMLTableElement>>(
{
current: null,
},
);

View File

@ -0,0 +1,12 @@
import { createContext } from 'react';
type RecordTableRowContextProps = {
pathToShowPage: string;
recordId: string;
rowIndex: number;
isSelected: boolean;
};
export const RecordTableRowContext = createContext<RecordTableRowContextProps>(
{} as RecordTableRowContextProps,
);

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const RowIdContext = createContext<string | null>(null);

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const RowIndexContext = createContext<number>(0);

View File

@ -1,31 +0,0 @@
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useScopedState } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopedState';
export const useRecordTableScopedStates = (recordTableId?: string) => {
const scopeId = useAvailableScopeIdOrThrow(
RecordTableScopeInternalContext,
recordTableId,
);
const {
getScopedState,
getScopedSelector,
getScopedFamilyState,
getScopedSnapshotValue,
getScopedSelectorSnapshotValue,
getScopedFamilySnapshotValue,
} = useScopedState(scopeId);
return {
scopeId,
injectStateWithRecordTableScopeId: getScopedState,
injectSelectorWithRecordTableScopeId: getScopedSelector,
injectFamilyStateWithRecordTableScopeId: getScopedFamilyState,
injectSelectorSnapshotValueWithRecordTableScopeId:
getScopedSelectorSnapshotValue,
injectSnapshotValueWithRecordTableScopeId: getScopedSnapshotValue,
injectFamilySnapshotValueWithRecordTableScopeId:
getScopedFamilySnapshotValue,
};
};

View File

@ -7,7 +7,6 @@ import { isSoftFocusActiveStateScopeMap } from '@/object-record/record-table/sta
import { isSoftFocusOnTableCellFamilyStateScopeMap } from '@/object-record/record-table/states/isSoftFocusOnTableCellFamilyStateScopeMap';
import { isTableCellInEditModeFamilyStateScopeMap } from '@/object-record/record-table/states/isTableCellInEditModeFamilyStateScopeMap';
import { numberOfTableRowsStateScopeMap } from '@/object-record/record-table/states/numberOfTableRowsStateScopeMap';
import { objectMetadataConfigStateScopeMap } from '@/object-record/record-table/states/objectMetadataConfigStateScopeMap';
import { onColumnsChangeStateScopeMap } from '@/object-record/record-table/states/onColumnsChangeStateScopeMap';
import { onEntityCountChangeStateScopeMap } from '@/object-record/record-table/states/onEntityCountChangeStateScopeMap';
import { resizeFieldOffsetStateScopeMap } from '@/object-record/record-table/states/resizeFieldOffsetStateScopeMap';
@ -44,10 +43,7 @@ export const useRecordTableStates = (recordTableId?: string) => {
getTableFiltersState: getState(tableFiltersStateScopeMap, scopeId),
getTableSortsState: getState(tableSortsStateScopeMap, scopeId),
getTableColumnsState: getState(tableColumnsStateScopeMap, scopeId),
getObjectMetadataConfigState: getState(
objectMetadataConfigStateScopeMap,
scopeId,
),
getOnColumnsChangeState: getState(onColumnsChangeStateScopeMap, scopeId),
getOnEntityCountChangeState: getState(
onEntityCountChangeStateScopeMap,

View File

@ -4,9 +4,11 @@ import { Key } from 'ts-key-enum';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useGetIsSomeCellInEditModeState } from '@/object-record/record-table/hooks/internal/useGetIsSomeCellInEditMode';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useUpsertRecordFromState } from '../../hooks/useUpsertRecordFromState';
import { ColumnDefinition } from '../types/ColumnDefinition';
@ -33,19 +35,27 @@ export const useRecordTable = (props?: useRecordTableProps) => {
getTableFiltersState,
getTableSortsState,
getTableColumnsState,
getObjectMetadataConfigState,
getOnEntityCountChangeState,
getSoftFocusPositionState,
getNumberOfTableRowsState,
getOnColumnsChangeState,
getIsRecordTableInitialLoadingState,
getTableLastRowVisibleState,
getNumberOfTableColumnsSelector,
getSelectedRowIdsSelector,
} = useRecordTableStates(recordTableId);
const setAvailableTableColumns = useSetRecoilState(
getAvailableTableColumnsState(),
const setAvailableTableColumns = useRecoilCallback(
({ snapshot, set }) =>
(columns: ColumnDefinition<FieldMetadata>[]) => {
const availableTableColumnsState = getSnapshotValue(
snapshot,
getAvailableTableColumnsState(),
);
if (isDeeplyEqual(availableTableColumnsState, columns)) {
return;
}
set(getAvailableTableColumnsState(), columns);
},
[getAvailableTableColumnsState],
);
const setOnEntityCountChange = useSetRecoilState(
@ -54,10 +64,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const setTableFilters = useSetRecoilState(getTableFiltersState());
const setObjectMetadataConfig = useSetRecoilState(
getObjectMetadataConfigState(),
);
const setTableSorts = useSetRecoilState(getTableSortsState());
const setTableColumns = useSetRecoilState(getTableColumnsState());
@ -113,160 +119,8 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const setSoftFocusPosition = useSetSoftFocusPosition(recordTableId);
const moveUp = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
let newRowNumber = softFocusPosition.row - 1;
if (newRowNumber < 0) {
newRowNumber = 0;
}
setSoftFocusPosition({
...softFocusPosition,
row: newRowNumber,
});
},
[getSoftFocusPositionState, setSoftFocusPosition],
);
const moveDown = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableRows = getSnapshotValue(
snapshot,
getNumberOfTableRowsState(),
);
let newRowNumber = softFocusPosition.row + 1;
if (newRowNumber >= numberOfTableRows) {
newRowNumber = numberOfTableRows - 1;
}
setSoftFocusPosition({
...softFocusPosition,
row: newRowNumber,
});
},
[
getNumberOfTableRowsState,
setSoftFocusPosition,
getSoftFocusPositionState,
],
);
const moveRight = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableColumns = getSnapshotValue(
snapshot,
getNumberOfTableColumnsSelector(),
);
const numberOfTableRows = getSnapshotValue(
snapshot,
getNumberOfTableRowsState(),
);
const currentColumnNumber = softFocusPosition.column;
const currentRowNumber = softFocusPosition.row;
const isLastRowAndLastColumn =
currentColumnNumber === numberOfTableColumns - 1 &&
currentRowNumber === numberOfTableRows - 1;
const isLastColumnButNotLastRow =
currentColumnNumber === numberOfTableColumns - 1 &&
currentRowNumber !== numberOfTableRows - 1;
const isNotLastColumn =
currentColumnNumber !== numberOfTableColumns - 1;
if (isLastRowAndLastColumn) {
return;
}
if (isNotLastColumn) {
setSoftFocusPosition({
row: currentRowNumber,
column: currentColumnNumber + 1,
});
} else if (isLastColumnButNotLastRow) {
setSoftFocusPosition({
row: currentRowNumber + 1,
column: 0,
});
}
},
[
getSoftFocusPositionState,
getNumberOfTableColumnsSelector,
getNumberOfTableRowsState,
setSoftFocusPosition,
],
);
const moveLeft = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableColumns = getSnapshotValue(
snapshot,
getNumberOfTableColumnsSelector(),
);
const currentColumnNumber = softFocusPosition.column;
const currentRowNumber = softFocusPosition.row;
const isFirstRowAndFirstColumn =
currentColumnNumber === 0 && currentRowNumber === 0;
const isFirstColumnButNotFirstRow =
currentColumnNumber === 0 && currentRowNumber > 0;
const isNotFirstColumn = currentColumnNumber > 0;
if (isFirstRowAndFirstColumn) {
return;
}
if (isNotFirstColumn) {
setSoftFocusPosition({
row: currentRowNumber,
column: currentColumnNumber - 1,
});
} else if (isFirstColumnButNotFirstRow) {
setSoftFocusPosition({
row: currentRowNumber - 1,
column: numberOfTableColumns - 1,
});
}
},
[
getNumberOfTableColumnsSelector,
getSoftFocusPositionState,
setSoftFocusPosition,
],
);
const { moveDown, moveLeft, moveRight, moveUp } =
useRecordTableMoveFocus(recordTableId);
const useMapKeyboardToSoftFocus = () => {
const disableSoftFocus = useDisableSoftFocus(recordTableId);
@ -333,7 +187,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setAvailableTableColumns,
setTableFilters,
setTableSorts,
setObjectMetadataConfig,
setOnEntityCountChange,
setRecordTableData,
setTableColumns,

View File

@ -0,0 +1,183 @@
import { useRecoilCallback } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition';
export const useRecordTableMoveFocus = (recordTableId?: string) => {
const {
scopeId,
getSoftFocusPositionState,
getNumberOfTableRowsState,
getNumberOfTableColumnsSelector,
getSelectedRowIdsSelector,
} = useRecordTableStates(recordTableId);
const setSoftFocusPosition = useSetSoftFocusPosition(recordTableId);
const moveUp = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
let newRowNumber = softFocusPosition.row - 1;
if (newRowNumber < 0) {
newRowNumber = 0;
}
setSoftFocusPosition({
...softFocusPosition,
row: newRowNumber,
});
},
[getSoftFocusPositionState, setSoftFocusPosition],
);
const moveDown = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableRows = getSnapshotValue(
snapshot,
getNumberOfTableRowsState(),
);
let newRowNumber = softFocusPosition.row + 1;
if (newRowNumber >= numberOfTableRows) {
newRowNumber = numberOfTableRows - 1;
}
setSoftFocusPosition({
...softFocusPosition,
row: newRowNumber,
});
},
[
getNumberOfTableRowsState,
setSoftFocusPosition,
getSoftFocusPositionState,
],
);
const moveRight = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableColumns = getSnapshotValue(
snapshot,
getNumberOfTableColumnsSelector(),
);
const numberOfTableRows = getSnapshotValue(
snapshot,
getNumberOfTableRowsState(),
);
const currentColumnNumber = softFocusPosition.column;
const currentRowNumber = softFocusPosition.row;
const isLastRowAndLastColumn =
currentColumnNumber === numberOfTableColumns - 1 &&
currentRowNumber === numberOfTableRows - 1;
const isLastColumnButNotLastRow =
currentColumnNumber === numberOfTableColumns - 1 &&
currentRowNumber !== numberOfTableRows - 1;
const isNotLastColumn =
currentColumnNumber !== numberOfTableColumns - 1;
if (isLastRowAndLastColumn) {
return;
}
if (isNotLastColumn) {
setSoftFocusPosition({
row: currentRowNumber,
column: currentColumnNumber + 1,
});
} else if (isLastColumnButNotLastRow) {
setSoftFocusPosition({
row: currentRowNumber + 1,
column: 0,
});
}
},
[
getSoftFocusPositionState,
getNumberOfTableColumnsSelector,
getNumberOfTableRowsState,
setSoftFocusPosition,
],
);
const moveLeft = useRecoilCallback(
({ snapshot }) =>
() => {
const softFocusPosition = getSnapshotValue(
snapshot,
getSoftFocusPositionState(),
);
const numberOfTableColumns = getSnapshotValue(
snapshot,
getNumberOfTableColumnsSelector(),
);
const currentColumnNumber = softFocusPosition.column;
const currentRowNumber = softFocusPosition.row;
const isFirstRowAndFirstColumn =
currentColumnNumber === 0 && currentRowNumber === 0;
const isFirstColumnButNotFirstRow =
currentColumnNumber === 0 && currentRowNumber > 0;
const isNotFirstColumn = currentColumnNumber > 0;
if (isFirstRowAndFirstColumn) {
return;
}
if (isNotFirstColumn) {
setSoftFocusPosition({
row: currentRowNumber,
column: currentColumnNumber - 1,
});
} else if (isFirstColumnButNotFirstRow) {
setSoftFocusPosition({
row: currentRowNumber - 1,
column: numberOfTableColumns - 1,
});
}
},
[
getNumberOfTableColumnsSelector,
getSoftFocusPositionState,
setSoftFocusPosition,
],
);
return {
scopeId,
moveDown,
moveLeft,
moveRight,
moveUp,
setSoftFocusPosition,
getSelectedRowIdsSelector,
};
};

View File

@ -4,22 +4,20 @@ 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 { 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 { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecordTable } from '../../hooks/useRecordTable';
import { useTableCell } from '../hooks/useTableCell';
import { TableCellContainer } from './RecordTableCellContainer';
export const RecordTableCell = ({
customHotkeyScope,
}: {
customHotkeyScope: HotkeyScope;
}) => {
const { closeTableCell } = useTableCell();
const { closeTableCell } = useCloseRecordTableCell();
const { entityId, fieldDefinition } = useContext(FieldContext);
const { moveLeft, moveRight, moveDown } = useRecordTable();
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
const handleEnter: FieldInputEvent = (persistField) => {
persistField();
@ -65,7 +63,7 @@ export const RecordTableCell = ({
};
return (
<TableCellContainer
<RecordTableCellContainer
editHotkeyScope={customHotkeyScope}
editModeContent={
<FieldInput

View File

@ -5,18 +5,18 @@ import { useRecoilValue } from 'recoil';
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useGetIsSomeCellInEditModeState } from '@/object-record/record-table/hooks/internal/useGetIsSomeCellInEditMode';
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
import { IconArrowUpRight } from '@/ui/display/icon';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { ColumnIndexContext } from '../../contexts/ColumnIndexContext';
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 { useTableCell } from '../hooks/useTableCell';
import { RecordTableCellButton } from './RecordTableCellButton';
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
@ -33,7 +33,7 @@ const StyledCellBaseContainer = styled.div`
user-select: none;
`;
export type TableCellContainerProps = {
export type RecordTableCellContainerProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
@ -49,28 +49,26 @@ const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const TableCellContainer = ({
export const RecordTableCellContainer = ({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
nonEditModeContent,
editHotkeyScope,
}: TableCellContainerProps) => {
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const { isSomeCellInEditModeState } = useRecordTable();
const isSomeCellInEditMode = useRecoilValue(isSomeCellInEditModeState());
}: RecordTableCellContainerProps) => {
const [isHovered, setIsHovered] = useState(false);
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const isSomeCellInEditModeState = useGetIsSomeCellInEditModeState();
const isSomeCellInEditMode = useRecoilValue(isSomeCellInEditModeState());
const moveSoftFocusToCurrentCellOnHover =
useMoveSoftFocusToCurrentCellOnHover();
const hasSoftFocus = useIsSoftFocusOnCurrentTableCell();
const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell();
const { openTableCell } = useTableCell();
const { openTableCell } = useOpenRecordTableCell();
const handleButtonClick = () => {
setSoftFocusOnCurrentTableCell();
@ -90,14 +88,11 @@ export const TableCellContainer = ({
const editModeContentOnly = useIsFieldInputOnly();
const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
const isEmpty = useIsFieldEmpty();
const isFirstColumn = useContext(ColumnIndexContext) === 0;
const { columnIndex } = useContext(RecordTableCellContext);
const isFirstColumn = columnIndex === 0;
const customButtonIcon = useGetButtonIcon();
const buttonIcon = isFirstColumn ? IconArrowUpRight : customButtonIcon;
const showButton =
@ -105,7 +100,7 @@ export const TableCellContainer = ({
hasSoftFocus &&
!isCurrentTableCellInEditMode &&
!editModeContentOnly &&
(!isFirstColumnCell || !isEmpty);
(!isFirstColumn || !isEmpty);
return (
<CellHotkeyScopeContext.Provider

View File

@ -1,7 +1,7 @@
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell';
import { useTableCell } from '../hooks/useTableCell';
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
@ -12,7 +12,7 @@ export const RecordTableCellDisplayMode = ({
const isFieldInputOnly = useIsFieldInputOnly();
const { openTableCell } = useTableCell();
const { openTableCell } = useOpenRecordTableCell();
const handleClick = () => {
setSoftFocusOnCurrentCell();

View File

@ -3,11 +3,11 @@ import { Key } from 'ts-key-enum';
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useTableCell } from '../hooks/useTableCell';
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
@ -16,7 +16,7 @@ type RecordTableCellSoftFocusModeProps = PropsWithChildren<unknown>;
export const RecordTableCellSoftFocusMode = ({
children,
}: RecordTableCellSoftFocusModeProps) => {
const { openTableCell } = useTableCell();
const { openTableCell } = useOpenRecordTableCell();
const isFieldInputOnly = useIsFieldInputOnly();
const toggleEditOnlyInput = useToggleEditOnlyInput();

View File

@ -1,58 +1,30 @@
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
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 { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { ColumnIndexContext } from '../../contexts/ColumnIndexContext';
import { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode';
export const useCloseRecordTableCell = () => {
const { getTableRowIdsState } = useRecordTableStates();
const { columnIndex } = useContext(RecordTableCellContext);
const { entityId, fieldDefinition } = useContext(FieldContext);
const deleteOneRecord = useContext(EntityDeleteContext);
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const useTableCell = () => {
const { getObjectMetadataConfigState, getTableRowIdsState } =
useRecordTableStates();
const { leaveTableFocus } = useRecordTable();
const objectMetadataConfig = useRecoilValue(getObjectMetadataConfigState());
const basePathToShowPage = objectMetadataConfig?.basePathToShowPage;
const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const navigate = useNavigate();
const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
const isEmpty = useIsFieldEmpty();
const { entityId, fieldDefinition } = useContext(FieldContext);
const deleteOneRecord = useContext(EntityDeleteContext);
const {
initDraftValue: initFieldInputDraftValue,
getDraftValueSelector: getFieldInputDraftValueSelector,
isDraftValueEmpty: isCurrentFieldInputValueEmpty,
} = useRecordFieldInput(
@ -63,34 +35,14 @@ export const useTableCell = () => {
getFieldInputDraftValueSelector(),
);
const isFirstColumnCell = columnIndex === 0;
const deleteRow = useRecoilCallback(({ snapshot }) => async () => {
const tableRowIds = getSnapshotValue(snapshot, getTableRowIdsState());
await deleteOneRecord(tableRowIds[0]);
});
const openTableCell = (options?: { initialValue?: string }) => {
if (isFirstColumnCell && !isEmpty && basePathToShowPage) {
leaveTableFocus();
navigate(`${basePathToShowPage}${entityId}`);
return;
}
setDragSelectionStartEnabled(false);
setCurrentTableCellInEditMode();
initFieldInputDraftValue(options?.initialValue);
if (customCellHotkeyScope) {
setHotkeyScope(
customCellHotkeyScope.scope,
customCellHotkeyScope.customScopes,
);
} else {
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
}
};
const closeTableCell = async () => {
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
@ -106,6 +58,5 @@ export const useTableCell = () => {
return {
closeTableCell,
openTableCell,
};
};

View File

@ -1,19 +1,20 @@
import { useContext, useMemo } from 'react';
import { ColumnIndexContext } from '../../contexts/ColumnIndexContext';
import { RowIndexContext } from '../../contexts/RowIndexContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { TableCellPosition } from '../../types/TableCellPosition';
export const useCurrentTableCellPosition = () => {
const currentRowNumber = useContext(RowIndexContext);
const currentColumnNumber = useContext(ColumnIndexContext);
const { rowIndex } = useContext(RecordTableRowContext);
const { columnIndex } = useContext(RecordTableCellContext);
const currentTableCellPosition: TableCellPosition = useMemo(
() => ({
column: currentColumnNumber,
row: currentRowNumber,
column: columnIndex,
row: rowIndex,
}),
[currentColumnNumber, currentRowNumber],
[columnIndex, rowIndex],
);
return currentTableCellPosition;

View File

@ -0,0 +1,88 @@
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilCallback } from 'recoil';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
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 { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode';
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode,
};
export const useOpenRecordTableCell = () => {
const { pathToShowPage } = useContext(RecordTableRowContext);
const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const navigate = useNavigate();
const leaveTableFocus = useLeaveTableFocus();
const { columnIndex } = useContext(RecordTableCellContext);
const isFirstColumnCell = columnIndex === 0;
const isEmpty = useIsFieldEmpty();
const { entityId, fieldDefinition } = useContext(FieldContext);
const { initDraftValue: initFieldInputDraftValue } = useRecordFieldInput(
`${entityId}-${fieldDefinition?.metadata?.fieldName}`,
);
const openTableCell = useRecoilCallback(
() => (options?: { initialValue?: string }) => {
if (isFirstColumnCell && !isEmpty) {
leaveTableFocus();
navigate(pathToShowPage);
return;
}
setDragSelectionStartEnabled(false);
setCurrentTableCellInEditMode();
initFieldInputDraftValue(options?.initialValue);
if (customCellHotkeyScope) {
setHotkeyScope(
customCellHotkeyScope.scope,
customCellHotkeyScope.customScopes,
);
} else {
setHotkeyScope(
DEFAULT_CELL_SCOPE.scope,
DEFAULT_CELL_SCOPE.customScopes,
);
}
},
[
isFirstColumnCell,
isEmpty,
leaveTableFocus,
navigate,
pathToShowPage,
setDragSelectionStartEnabled,
setCurrentTableCellInEditMode,
initFieldInputDraftValue,
customCellHotkeyScope,
setHotkeyScope,
],
);
return {
openTableCell,
};
};

View File

@ -1,14 +1,14 @@
import { useRecoilCallback } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useSetSoftFocusPosition } from '@/object-record/record-table/hooks/internal/useSetSoftFocusPosition';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const useSetSoftFocus = () => {
const { setSoftFocusPosition } = useRecordTable();
const setSoftFocusPosition = useSetSoftFocusPosition();
const { getIsSoftFocusActiveState } = useRecordTableStates();

View File

@ -1,41 +0,0 @@
import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { RowIdContext } from '../../contexts/RowIdContext';
export const useCurrentRowSelected = () => {
const currentRowId = useContext(RowIdContext);
const { isRowSelectedFamilyState } = useRecordTableStates();
const isRowSelected = useRecoilValue(
isRowSelectedFamilyState(currentRowId ?? ''),
);
const setCurrentRowSelected = useRecoilCallback(
({ set, snapshot }) =>
(newSelectedState: boolean) => {
if (!currentRowId) return;
const isRowSelected = getSnapshotValue(
snapshot,
isRowSelectedFamilyState(currentRowId),
);
if (newSelectedState && !isRowSelected) {
set(isRowSelectedFamilyState(currentRowId), true);
} else if (!newSelectedState && isRowSelected) {
set(isRowSelectedFamilyState(currentRowId), false);
}
},
[currentRowId, isRowSelectedFamilyState],
);
return {
currentRowSelected: isRowSelected,
setCurrentRowSelected,
};
};

View File

@ -0,0 +1,31 @@
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
export const useSetCurrentRowSelected = () => {
const { recordId } = useContext(RecordTableRowContext);
const { isRowSelectedFamilyState } = useRecordTableStates();
const setCurrentRowSelected = useRecoilCallback(
({ set, snapshot }) =>
(newSelectedState: boolean) => {
const isRowSelected = getSnapshotValue(
snapshot,
isRowSelectedFamilyState(recordId),
);
if (isRowSelected !== newSelectedState) {
set(isRowSelectedFamilyState(recordId), newSelectedState);
}
},
[recordId, isRowSelectedFamilyState],
);
return {
setCurrentRowSelected,
};
};

View File

@ -1,8 +0,0 @@
import { ObjectMetadataConfig } from '@/object-record/record-table/types/ObjectMetadataConfig';
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
export const objectMetadataConfigStateScopeMap =
createStateScopeMap<ObjectMetadataConfig | null>({
key: 'objectMetadataConfigStateScopeMap',
defaultValue: null,
});

View File

@ -1,6 +1,6 @@
import { availableTableColumnsStateScopeMap } from '@/object-record/record-table/states/availableTableColumnsStateScopeMap';
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { availableTableColumnsStateScopeMap } from '../availableTableColumnsStateScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
export const visibleTableColumnsSelectorScopeMap =

View File

@ -1,4 +0,0 @@
export type ObjectMetadataConfig = {
labelIdentifierFieldMetadataId: string;
basePathToShowPage: string;
};

View File

@ -15,6 +15,7 @@ const StyledContainer = styled.div`
export const SignInBackgroundMockContainer = () => {
const objectNamePlural = 'companies';
const objectNameSingular = 'company';
const recordTableId = 'sign-up-mock-record-table-id';
const viewBarId = 'companies-mock';
@ -33,7 +34,7 @@ export const SignInBackgroundMockContainer = () => {
viewId={viewBarId}
/>
<RecordTableWithWrappers
objectNamePlural={objectNamePlural}
objectNameSingular={objectNameSingular}
recordTableId={recordTableId}
viewBarId={viewBarId}
createRecord={async () => {}}

View File

@ -30,7 +30,6 @@ export const SignInBackgroundMockContainerEffect = ({
setOnEntityCountChange,
setRecordTableData,
setTableColumns,
setObjectMetadataConfig,
} = useRecordTable({
recordTableId,
});
@ -81,10 +80,6 @@ export const SignInBackgroundMockContainerEffect = ({
setTableColumns,
]);
useEffect(() => {
setObjectMetadataConfig?.(mockIdentifier);
}, [setObjectMetadataConfig]);
const { setActionBarEntries, setContextMenuEntries } =
useRecordTableContextMenuEntries({
objectNamePlural,
@ -104,8 +99,3 @@ export const SignInBackgroundMockContainerEffect = ({
return <></>;
};
const mockIdentifier = {
basePathToShowPage: '/object/company/',
labelIdentifierFieldMetadataId: '20202020-6d30-4111-9f40-b4301906fd3c',
};

View File

@ -1,7 +1,6 @@
import { useInView } from 'react-intersection-observer';
import styled from '@emotion/styled';
import { StyledRow } from '@/object-record/record-table/components/RecordTableRow';
import { grayScale } from '@/ui/theme/constants/colors';
type FetchMoreLoaderProps = {
@ -30,12 +29,12 @@ export const FetchMoreLoader = ({
return (
<tbody ref={tbodyRef}>
{loading && (
<StyledRow selected={false}>
<tr>
<td colSpan={7}>
<StyledText>Loading more...</StyledText>
</td>
<td colSpan={7} />
</StyledRow>
</tr>
)}
</tbody>
);

View File

@ -9,8 +9,8 @@ import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObj
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useTableCell';
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageBody } from '@/ui/layout/page/PageBody';