diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-04-03 03:21:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-02 17:21:15 -0700 |
commit | 87da619aaa92e0ec762e68c13b24e58a25da10a8 (patch) | |
tree | 4da902d3ca43a226f6da8e5c090ab33c2df3297a /src/screens/Search/modules/ExploreFeedPreviews.tsx | |
parent | 8d1f97b5ffac5d86762f1d4e9384ff3097acbc52 (diff) | |
download | voidsky-87da619aaa92e0ec762e68c13b24e58a25da10a8.tar.zst |
[Explore] Base (#8053)
* migrate to #/screens * rm unneeded import * block drawer gesture on recent profiles * rm recommendations (#8056) * [Explore] Disable Trending videos (#8054) * remove giant header * disable * [Explore] Dynamic module ordering (#8066) * Dynamic module ordering * [Explore] New headers, metrics (#8067) * new sticky headers * improve spacing between modules * view metric on modules * update metrics names * [Explore] Suggested accounts module (#8072) * use modern profile card, update load more * add tab bar * tabbed suggested accounts * [Explore] Discover feeds module (#8073) * cap number of feeds to 3 * change feed pin button * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * restore statsig to log events * filter out followed profiles, make suer enough are loaded (#8090) * [Explore] Trending topics (#8055) * redesigned trending topics * rm borders on web * get post count / age / ranking from api * spacing tweaks * fetch more topics then slice * use api data for avis/category * rm top border * Integrate new SDK, part out components * Clean up * Use status field * Bump SDK * Send up interests and langs --------- Co-authored-by: Eric Bailey <git@esb.lol> * Clean up module spacing and borders (cherry picked from commit 63d19b6c2d67e226e0e14709b1047a1f88b3ce1c) (cherry picked from commit 62d7d394ab1dc31b40b9c2cf59075adbf94737a1) * Switch back border ordering (cherry picked from commit 34e3789f8b410132c1390df3c2bb8257630ebdd9) * [Explore] Starter Packs (#8095) * Temp WIP (cherry picked from commit 43b5d7b1e64b3adb1ed162262d0310e0bf026c18) * New SP card * Load state * Revert change * Cleanup * Interests and caching * Count total * Format * Caching * [Explore] Feed previews module (#8075) * wip new hook * get fetching working, maybe * get feed previews rendering! * fix header height * working pin button * extract out FeedLink * add loader * only make preview:header sticky * Fix headers * Header tweaks * Fix moderation filter * Fix threading --------- Co-authored-by: Eric Bailey <git@esb.lol> * Space it out * Fix query key * Mock new endpoint, filter saved feeds * Make sure we're pinning, lower cache time * add news category * Remove log * Improve suggested accounts load state * Integrate new app view endpoint * fragment * Update src/screens/Search/modules/ExploreTrendingTopics.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Search/modules/ExploreTrendingTopics.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * lint * maybe fix this --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Hailey <me@haileyok.com>
Diffstat (limited to 'src/screens/Search/modules/ExploreFeedPreviews.tsx')
-rw-r--r-- | src/screens/Search/modules/ExploreFeedPreviews.tsx | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/screens/Search/modules/ExploreFeedPreviews.tsx b/src/screens/Search/modules/ExploreFeedPreviews.tsx new file mode 100644 index 000000000..30aa00a3f --- /dev/null +++ b/src/screens/Search/modules/ExploreFeedPreviews.tsx @@ -0,0 +1,264 @@ +import {useMemo} from 'react' +import {type AppBskyFeedDefs, moderatePost} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useInfiniteQuery} from '@tanstack/react-query' + +import {CustomFeedAPI} from '#/lib/api/feed/custom' +import {aggregateUserInterests} from '#/lib/api/feed/utils' +import {FeedTuner} from '#/lib/api/feed-manip' +import {cleanError} from '#/lib/strings/errors' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import { + type FeedPostSlice, + type FeedPostSliceItem, +} from '#/state/queries/post-feed' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {useAgent} from '#/state/session' + +const RQKEY_ROOT = 'feed-previews' +const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds] + +const LIMIT = 8 // sliced to 6, overfetch to account for moderation + +export type FeedPreviewItem = + | { + type: 'topBorder' + key: string + } + | { + type: 'preview:loading' + key: string + } + | { + type: 'preview:error' + key: string + message: string + error: string + } + | { + type: 'preview:loadMoreError' + key: string + } + | { + type: 'preview:empty' + key: string + } + | { + type: 'preview:header' + key: string + feed: AppBskyFeedDefs.GeneratorView + } + | { + type: 'preview:footer' + key: string + } + // copied from PostFeed.tsx + | { + type: 'preview:sliceItem' + key: string + slice: FeedPostSlice + indexInSlice: number + showReplyTo: boolean + hideTopBorder: boolean + } + | { + type: 'preview:sliceViewFullThread' + key: string + uri: string + } + +export function useFeedPreviews(feeds: AppBskyFeedDefs.GeneratorView[]) { + const uris = feeds.map(feed => feed.uri) + const {_} = useLingui() + const agent = useAgent() + const {data: preferences} = usePreferencesQuery() + const userInterests = aggregateUserInterests(preferences) + const moderationOpts = useModerationOpts() + const enabled = feeds.length > 0 + + const query = useInfiniteQuery({ + enabled, + queryKey: RQKEY(uris), + queryFn: async ({pageParam}) => { + const feed = feeds[pageParam] + const api = new CustomFeedAPI({ + agent, + feedParams: {feed: feed.uri}, + userInterests, + }) + const data = await api.fetch({cursor: undefined, limit: LIMIT}) + return { + feed, + posts: data.feed, + } + }, + initialPageParam: 0, + getNextPageParam: (_p, _a, count) => + count < feeds.length ? count + 1 : undefined, + }) + + const {data, isFetched, isError, isPending, error} = query + + return { + query, + data: useMemo<FeedPreviewItem[]>(() => { + const items: FeedPreviewItem[] = [] + + if (!enabled) return items + + const isEmpty = + !isPending && !data?.pages?.some(page => page.posts.length) + + if (isFetched) { + if (isError && isEmpty) { + items.push({ + type: 'preview:error', + key: 'error', + message: _(msg`An error occurred while fetching the feed.`), + error: cleanError(error), + }) + } else if (isEmpty) { + items.push({ + type: 'preview:empty', + key: 'empty', + }) + } else if (data) { + for (let pageIndex = 0; pageIndex < data.pages.length; pageIndex++) { + const page = data.pages[pageIndex] + // default feed tuner - we just want it to slice up the feed + const tuner = new FeedTuner([]) + const slices: FeedPreviewItem[] = [] + + let rowIndex = 0 + for (const item of tuner.tune(page.posts)) { + if (item.isFallbackMarker) continue + + const moderations = item.items.map(item => + moderatePost(item.post, moderationOpts!), + ) + + // apply moderation filters + item.items = item.items.filter((_, i) => { + return !moderations[i]?.ui('contentList').filter + }) + + const slice = { + _reactKey: item._reactKey, + _isFeedPostSlice: true, + isFallbackMarker: false, + isIncompleteThread: item.isIncompleteThread, + feedContext: item.feedContext, + reason: item.reason, + feedPostUri: item.feedPostUri, + items: item.items.slice(0, 6).map((subItem, i) => { + const feedPostSliceItem: FeedPostSliceItem = { + _reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`, + uri: subItem.post.uri, + post: subItem.post, + record: subItem.record, + moderation: moderations[i], + parentAuthor: subItem.parentAuthor, + isParentBlocked: subItem.isParentBlocked, + isParentNotFound: subItem.isParentNotFound, + } + return feedPostSliceItem + }), + } + if (slice.isIncompleteThread && slice.items.length >= 3) { + const beforeLast = slice.items.length - 2 + const last = slice.items.length - 1 + slices.push({ + type: 'preview:sliceItem', + key: slice.items[0]._reactKey, + slice: slice, + indexInSlice: 0, + showReplyTo: false, + hideTopBorder: rowIndex === 0, + }) + slices.push({ + type: 'preview:sliceViewFullThread', + key: slice._reactKey + '-viewFullThread', + uri: slice.items[0].uri, + }) + slices.push({ + type: 'preview:sliceItem', + key: slice.items[beforeLast]._reactKey, + slice: slice, + indexInSlice: beforeLast, + showReplyTo: + slice.items[beforeLast].parentAuthor?.did !== + slice.items[beforeLast].post.author.did, + hideTopBorder: false, + }) + slices.push({ + type: 'preview:sliceItem', + key: slice.items[last]._reactKey, + slice: slice, + indexInSlice: last, + showReplyTo: false, + hideTopBorder: false, + }) + } else { + for (let i = 0; i < slice.items.length; i++) { + slices.push({ + type: 'preview:sliceItem', + key: slice.items[i]._reactKey, + slice: slice, + indexInSlice: i, + showReplyTo: i === 0, + hideTopBorder: i === 0 && rowIndex === 0, + }) + } + } + + rowIndex++ + } + + if (slices.length > 0) { + if (pageIndex > 0) { + items.push({ + type: 'topBorder', + key: `topBorder-${page.feed.uri}`, + }) + } + items.push( + { + type: 'preview:footer', + key: `footer-${page.feed.uri}`, + }, + { + type: 'preview:header', + key: `header-${page.feed.uri}`, + feed: page.feed, + }, + ...slices, + ) + } + } + } else if (isError && !isEmpty) { + items.push({ + type: 'preview:loadMoreError', + key: 'loadMoreError', + }) + } + } else { + items.push({ + type: 'preview:loading', + key: 'loading', + }) + } + + return items + }, [ + enabled, + data, + isFetched, + isError, + isPending, + moderationOpts, + _, + error, + ]), + } +} |