From 53c958ea858641311a1cdd70e3c73a1ee2ca80ca Mon Sep 17 00:00:00 2001 From: Patricio Vicens Date: Wed, 24 Apr 2024 17:31:22 -0300 Subject: [PATCH] add multi shimmer skeleton --- .../components/MultiShimmer/MultiShimmer.tsx | 35 ++++++ .../Profile/ProfileScreenLoadingSkeleton.tsx | 4 +- .../src/contexts/shimmer/ShimmerContext.tsx | 84 +++++++++++++- apps/web/src/hooks/useNftRetry.tsx | 2 +- .../UserGalleryCollections.tsx | 104 ++++++++++-------- 5 files changed, 176 insertions(+), 53 deletions(-) create mode 100644 apps/web/src/components/MultiShimmer/MultiShimmer.tsx diff --git a/apps/web/src/components/MultiShimmer/MultiShimmer.tsx b/apps/web/src/components/MultiShimmer/MultiShimmer.tsx new file mode 100644 index 0000000000..421bdf2948 --- /dev/null +++ b/apps/web/src/components/MultiShimmer/MultiShimmer.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; + +const Wrapper = styled.div` + margin-bottom: 60px; + width: 100%; +`; + +const ArtworksGridSkeleton = styled.div` + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 32px; + margin-top: 16px; +`; + +const ArtworkGridItemSkeleton = styled(Skeleton)` + height: 300px; +`; + +const SkeletoState = () => { + return ( + + + {[...Array(12)].map((_, index) => ( + + ))} + + + ); +}; + +export function MultiShimmer() { + return ; +} diff --git a/apps/web/src/components/Profile/ProfileScreenLoadingSkeleton.tsx b/apps/web/src/components/Profile/ProfileScreenLoadingSkeleton.tsx index 122e60ef6e..0b2f92db32 100644 --- a/apps/web/src/components/Profile/ProfileScreenLoadingSkeleton.tsx +++ b/apps/web/src/components/Profile/ProfileScreenLoadingSkeleton.tsx @@ -42,7 +42,7 @@ const UsernameSocialsSkeleton = styled(Skeleton)` const ArtworksGridSkeleton = styled.div` display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(4, 1fr); grid-gap: 16px; margin-top: 16px; `; @@ -66,7 +66,7 @@ const UserGallerySkeleton = () => { - {[...Array(6)].map((_, index) => ( + {[...Array(12)].map((_, index) => ( ))} diff --git a/apps/web/src/contexts/shimmer/ShimmerContext.tsx b/apps/web/src/contexts/shimmer/ShimmerContext.tsx index 968f911842..d41236987b 100644 --- a/apps/web/src/contexts/shimmer/ShimmerContext.tsx +++ b/apps/web/src/contexts/shimmer/ShimmerContext.tsx @@ -1,6 +1,15 @@ -import { createContext, memo, ReactNode, useContext, useMemo, useState } from 'react'; +import { + createContext, + memo, + ReactNode, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; import styled from 'styled-components'; +import { MultiShimmer } from '~/components/MultiShimmer/MultiShimmer'; import Shimmer from '~/components/Shimmer/Shimmer'; type AspectRatio = 'wide' | 'square' | 'tall' | 'unknown'; @@ -23,7 +32,7 @@ export const useContentState = (): ShimmerState => { // TODO(Terence): Fix this later // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ContentIsLoadedEvent = (event?: any) => void; +export type ContentIsLoadedEvent = (event?: any, tokenId?: string) => void; type ShimmerAction = { setContentIsLoaded: ContentIsLoadedEvent; @@ -40,6 +49,67 @@ export const useSetContentIsLoaded = (): ShimmerAction['setContentIsLoaded'] => return context.setContentIsLoaded; }; +const MultiShimmerContext = createContext< + { markTokenAsLoaded: (tokenId: string) => void } | undefined +>(undefined); + +export const useMultiShimmerProvider = () => { + const context = useContext(MultiShimmerContext); + if (!context) { + return null; + } + return context; +}; + +export const MultiShimmerProvider = ({ + children, + tokenIdsToLoad, +}: { + children: ReactNode; + tokenIdsToLoad: string[]; +}) => { + const [waitingForTokenIds, setWaitingForTokenIds] = useState(tokenIdsToLoad); + + const markTokenAsLoaded = useCallback((tokenId: string) => { + setWaitingForTokenIds((prev) => prev.filter((id) => id !== tokenId)); + }, []); + + const value = useMemo( + () => ({ + markTokenAsLoaded, + }), + [markTokenAsLoaded] + ); + + type VisibleDivProps = { + isVisible: boolean; + }; + + const VisibleDiv = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')}; + `; + + const isLoading = Boolean(waitingForTokenIds.length); + + return ( + + {isLoading && ( + + + + + + )} + {children} + + ); +}; + type Props = { children: ReactNode | ReactNode[] }; const ShimmerProvider = memo(({ children }: Props) => { @@ -47,6 +117,8 @@ const ShimmerProvider = memo(({ children }: Props) => { const [aspectRatio, setAspectRatio] = useState(null); const [aspectRatioType, setAspectRatioType] = useState(null); + const multiShimmerContext = useMultiShimmerProvider(); + const state = useMemo( () => ({ aspectRatio, @@ -61,7 +133,7 @@ const ShimmerProvider = memo(({ children }: Props) => { // such as images, videos, and iframes, each of which come with different properties. // This is getting fixed in a followup PR // eslint-disable-next-line @typescript-eslint/no-explicit-any - setContentIsLoaded: (event?: any) => { + setContentIsLoaded: (event?: any, tokenId?: string) => { if (event) { // default aspect ratio to 1; if we can't determine an asset's dimensions, we'll show it in a square viewport let aspectRatio = 1; @@ -89,11 +161,13 @@ const ShimmerProvider = memo(({ children }: Props) => { setAspectRatioType('unknown'); } } - + if (tokenId) { + multiShimmerContext?.markTokenAsLoaded(tokenId); + } setIsLoaded(true); }, }), - [] + [multiShimmerContext] ); return ( diff --git a/apps/web/src/hooks/useNftRetry.tsx b/apps/web/src/hooks/useNftRetry.tsx index fbf20f0bba..b7308460ff 100644 --- a/apps/web/src/hooks/useNftRetry.tsx +++ b/apps/web/src/hooks/useNftRetry.tsx @@ -30,7 +30,7 @@ export function useNftRetry({ tokenId }: useNftRetryArgs): useNftRetryResult { const handleNftLoaded = useCallback( (event) => { - shimmerContext?.setContentIsLoaded(event); + shimmerContext?.setContentIsLoaded(event, tokenId); markTokenAsLoaded(tokenId); }, diff --git a/apps/web/src/scenes/UserGalleryPage/UserGalleryCollections.tsx b/apps/web/src/scenes/UserGalleryPage/UserGalleryCollections.tsx index a9b57cc6ec..dd4571f88f 100644 --- a/apps/web/src/scenes/UserGalleryPage/UserGalleryCollections.tsx +++ b/apps/web/src/scenes/UserGalleryPage/UserGalleryCollections.tsx @@ -13,6 +13,7 @@ import styled from 'styled-components'; import breakpoints from '~/components/core/breakpoints'; import { DisplayLayout } from '~/components/core/enums'; +import { MultiShimmerProvider } from '~/contexts/shimmer/ShimmerContext'; import { UserGalleryCollectionsFragment$key } from '~/generated/UserGalleryCollectionsFragment.graphql'; import { UserGalleryCollectionsQueryFragment$key } from '~/generated/UserGalleryCollectionsQueryFragment.graphql'; import useWindowSize from '~/hooks/useWindowSize'; @@ -58,6 +59,9 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) { tokens { __typename id + token { + dbid + } } layout { sectionLayout { @@ -71,6 +75,14 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) { galleryRef ); + const tokenIds = collections?.reduce((acc, collection) => { + const ids = + collection?.tokens + ?.map((token) => token?.token?.dbid) + .filter((id): id is string => id !== undefined) || []; + return acc.concat(ids); + }, [] as string[]); + const isAuthenticatedUsersPage = loggedInUserId === owner?.id; const nonNullCollections = removeNullValues(collections); @@ -147,58 +159,60 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) { } return ( - - - {({ height, registerChild, scrollTop, onChildScroll }) => ( - - {({ width }) => ( - // @ts-expect-error shitty react-virtualized types -
- ({ - overscanStartIndex: Math.max(0, startIndex - overscanCellsCount), - overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount), - })} - /> -
- )} -
- )} -
-
+ + + + {({ height, registerChild, scrollTop, onChildScroll }) => ( + + {({ width }) => ( + // @ts-expect-error shitty react-virtualized types +
+ ({ + overscanStartIndex: Math.max(0, startIndex - overscanCellsCount), + overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount), + })} + /> +
+ )} +
+ )} +
+
+
); } const StyledUserGalleryCollections = styled.div` - width: 100%; + width: 100%; - padding-top: 16px; + padding-top: 16px; - @media only screen and ${breakpoints.tablet} { - padding-top: 24px; + @media only screen and ${breakpoints.tablet} { + padding-top: 24px; } -`; + `; const StyledUserGalleryCollectionContainer = styled.div` padding-bottom: 48px;