diff --git a/app/soapbox/features/feed-filtering/feed-carousel.tsx b/app/soapbox/features/feed-filtering/feed-carousel.tsx index 481ae138e..23f417959 100644 --- a/app/soapbox/features/feed-filtering/feed-carousel.tsx +++ b/app/soapbox/features/feed-filtering/feed-carousel.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { replaceHomeTimeline } from 'soapbox/actions/timelines'; @@ -9,7 +9,10 @@ import { Avatar, useCarouselAvatars, useMarkAsSeen } from 'soapbox/queries/carou import { Card, HStack, Icon, Stack, Text } from '../../components/ui'; import PlaceholderAvatar from '../placeholder/components/placeholder-avatar'; -const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolean, onViewed: (account_id: string) => void }) => { +const CarouselItem = React.forwardRef(( + { avatar, seen, onViewed, onPinned }: { avatar: Avatar, seen: boolean, onViewed: (account_id: string) => void, onPinned?: (avatar: null | Avatar) => void }, + ref: any, +) => { const dispatch = useAppDispatch(); const markAsSeen = useMarkAsSeen(); @@ -28,7 +31,15 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea if (isSelected) { dispatch(replaceHomeTimeline(null, { maxId: null }, () => setLoading(false))); + + if (onPinned) { + onPinned(null); + } } else { + if (onPinned) { + onPinned(avatar); + } + onViewed(avatar.account_id); markAsSeen.mutate(avatar.account_id); dispatch(replaceHomeTimeline(avatar.account_id, { maxId: null }, () => setLoading(false))); @@ -37,14 +48,15 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea return (
- -
+ +
{isSelected && (
@@ -54,7 +66,7 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea
); -}; +}); const FeedCarousel = () => { const { data: avatars, isFetching, isError } = useCarouselAvatars(); - const [cardRef, setCardRef, { width }] = useDimensions(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_ref, setContainerRef, { width }] = useDimensions(); const [seenAccountIds, setSeenAccountIds] = useState([]); const [pageSize, setPageSize] = useState(0); const [currentPage, setCurrentPage] = useState(1); + const [pinnedAvatar, setPinnedAvatar] = useState(null); + + const avatarsToList = useMemo(() => { + const list = avatars.filter((avatar) => avatar.account_id !== pinnedAvatar?.account_id); + if (pinnedAvatar) { + return [null, ...list]; + } + + return list; + }, [avatars, pinnedAvatar]); const numberOfPages = Math.ceil(avatars.length / pageSize); - const widthPerAvatar = (cardRef?.scrollWidth || 0) / avatars.length; + const widthPerAvatar = width / (Math.floor(width / 80)); const hasNextPage = currentPage < numberOfPages && numberOfPages > 1; const hasPrevPage = currentPage > 1 && numberOfPages > 1; @@ -118,67 +141,100 @@ const FeedCarousel = () => { ); } - if (avatars.length === 0) { - return null; - } - return ( - -
- {hasPrevPage && ( -
-
- -
-
- )} +
+ +
+ +
- - {isFetching ? ( - new Array(pageSize).fill(0).map((_, idx) => ( -
- -
- )) - ) : ( - avatars.map((avatar) => ( +
+ {pinnedAvatar ? ( +
setPinnedAvatar(avatar)} /> - )) - )} - - - {hasNextPage && ( -
-
-
-
- )} -
- + ) : null} + + + {isFetching ? ( + new Array(20).fill(0).map((_, idx) => ( +
+ +
+ )) + ) : ( + avatarsToList.map((avatar: any, index) => ( +
+ {avatar === null ? ( + +
+
+
+ + ) : ( + { + setPinnedAvatar(null); + setTimeout(() => { + setPinnedAvatar(avatar); + }, 1); + }} + onViewed={markAsSeen} + /> + )} +
+ )) + )} + +
+ +
+ +
+
+
); }; diff --git a/app/soapbox/features/placeholder/components/placeholder-avatar.tsx b/app/soapbox/features/placeholder/components/placeholder-avatar.tsx index 7d9fa62f0..9d2e1a3ec 100644 --- a/app/soapbox/features/placeholder/components/placeholder-avatar.tsx +++ b/app/soapbox/features/placeholder/components/placeholder-avatar.tsx @@ -21,14 +21,14 @@ const PlaceholderAvatar: React.FC = ({ size, withText = fals }, [size]); return ( - +
{withText && ( -
+
)} );