Prefetch Skeleton Loading on Indexes and Shows (#5545)

### Description
Prefetch Skeleton Loading on Indexes and Shows

### Refs
#4458

### Demo

https://jam.dev/c/a1ad04e1-80b6-4b2a-b7df-373f52f4b169

https://jam.dev/c/c5038b97-2f18-4c29-8dee-18c09376e5ee

Fixes: #4458

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Matheus <matheus_benini@hotmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
gitstart-twenty
2024-05-27 15:56:08 +08:00
committed by GitHub
parent cfd83d6b8e
commit 9c046dcfdb
60 changed files with 490 additions and 161 deletions

View File

@ -0,0 +1,31 @@
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
const StyledSkeletonDiv = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
height: 24px;
`;
export const PropertyBoxSkeletonLoader = () => {
const theme = useTheme();
const skeletonItems = Array.from({ length: 4 }).map((_, index) => ({
id: `skeleton-item-${index}`,
}));
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
{skeletonItems.map(({ id }) => (
<StyledSkeletonDiv key={id}>
<Skeleton width={92} height={16} />
<Skeleton width={154} height={16} />
</StyledSkeletonDiv>
))}
</SkeletonTheme>
);
};

View File

@ -12,6 +12,7 @@ import {
} from '@/object-record/record-field/contexts/FieldContext';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
@ -19,6 +20,7 @@ import { recordLoadingFamilyState } from '@/object-record/record-store/states/re
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
@ -127,6 +129,7 @@ export const RecordShowContainer = ({
);
const isReadOnly = objectMetadataItem.isRemote;
const isPrefetchLoading = useIsPrefetchLoading();
return (
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
@ -139,7 +142,7 @@ export const RecordShowContainer = ({
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
avatarPlaceholder={recordIdentifier?.name ?? ''}
date={recordFromStore.createdAt ?? ''}
loading={loading || recordLoading}
loading={isPrefetchLoading || loading || recordLoading}
title={
<FieldContext.Provider
value={{
@ -176,32 +179,36 @@ export const RecordShowContainer = ({
}
/>
<PropertyBox>
{inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
<FieldContext.Provider
key={objectRecordId + fieldMetadataItem.id}
value={{
entityId: objectRecordId,
maxWidth: 200,
recoilScopeId: objectRecordId + fieldMetadataItem.id,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: index,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell
loading={loading || recordLoading}
readonly={isReadOnly}
/>
</FieldContext.Provider>
))}
{isPrefetchLoading ? (
<PropertyBoxSkeletonLoader />
) : (
inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
<FieldContext.Provider
key={objectRecordId + fieldMetadataItem.id}
value={{
entityId: objectRecordId,
maxWidth: 200,
recoilScopeId: objectRecordId + fieldMetadataItem.id,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: index,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell
loading={loading || recordLoading}
readonly={isReadOnly}
/>
</FieldContext.Provider>
))
)}
</PropertyBox>
<RecordDetailDuplicatesSection
objectRecordId={objectRecordId}
@ -224,7 +231,7 @@ export const RecordShowContainer = ({
}}
>
<RecordDetailRelationSection
loading={loading || recordLoading}
loading={isPrefetchLoading || loading || recordLoading}
/>
</FieldContext.Provider>
))}
@ -241,7 +248,7 @@ export const RecordShowContainer = ({
tasks
notes
emails
loading={loading || recordLoading}
loading={isPrefetchLoading || loading || recordLoading}
/>
) : (
<></>

View File

@ -1,6 +1,4 @@
import { useCallback, useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import qs from 'qs';
import { useRecoilValue } from 'recoil';
@ -13,6 +11,7 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
import { RecordDetailRelationRecordsListEmptyState } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState';
import { RecordDetailRelationSectionSkeletonLoader } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader';
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
@ -37,25 +36,6 @@ const StyledAddDropdown = styled(Dropdown)`
margin-left: auto;
`;
const StyledSkeletonDiv = styled.div`
height: 40px;
`;
const StyledRecordDetailRelationSectionSkeletonLoader = () => {
const theme = useTheme();
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<StyledSkeletonDiv>
<Skeleton width={129} height={16} />
</StyledSkeletonDiv>
</SkeletonTheme>
);
};
export const RecordDetailRelationSection = ({
loading,
}: RecordDetailRelationSectionProps) => {
@ -142,7 +122,11 @@ export const RecordDetailRelationSection = ({
const showContent = () => {
if (loading) {
return <StyledRecordDetailRelationSectionSkeletonLoader />;
return (
<RecordDetailRelationSectionSkeletonLoader
numSkeletons={fieldName === 'people' ? 2 : 1}
/>
);
}
return relationRecords.length ? (

View File

@ -0,0 +1,31 @@
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
const StyledSkeletonDiv = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
height: 40px;
`;
export const RecordDetailRelationSectionSkeletonLoader = ({
numSkeletons = 1,
}: {
numSkeletons?: number;
}) => {
const theme = useTheme();
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<StyledSkeletonDiv>
{Array.from({ length: numSkeletons }).map((_, index) => (
<Skeleton key={index} width={129} height={16} />
))}
</StyledSkeletonDiv>
</SkeletonTheme>
);
};