diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/FeedCard.tsx | 100 | ||||
-rw-r--r-- | src/components/ListCard.tsx | 129 | ||||
-rw-r--r-- | src/components/StarterPack/Main/FeedsList.tsx | 2 |
3 files changed, 160 insertions, 71 deletions
diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index e0fc7ef54..b1200d9c4 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -18,7 +18,7 @@ import { useRemoveFeedMutation, } from '#/state/queries/preferences' import {sanitizeHandle} from 'lib/strings/handles' -import {precacheFeedFromGeneratorView, precacheList} from 'state/queries/feed' +import {precacheFeedFromGeneratorView} from 'state/queries/feed' import {useSession} from 'state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import * as Toast from 'view/com/util/Toast' @@ -33,45 +33,31 @@ import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -type Props = - | { - type: 'feed' - view: AppBskyFeedDefs.GeneratorView - } - | { - type: 'list' - view: AppBskyGraphDefs.ListView - } +type Props = { + view: AppBskyFeedDefs.GeneratorView +} export function Default(props: Props) { - const {type, view} = props - const displayName = type === 'feed' ? view.displayName : view.name - const purpose = type === 'list' ? view.purpose : undefined + const {view} = props return ( - <Link label={displayName} {...props}> + <Link label={view.displayName} {...props}> <Outer> <Header> <Avatar src={view.avatar} /> - <TitleAndByline - title={displayName} - creator={view.creator} - type={type} - purpose={purpose} - /> - <Action uri={view.uri} pin type={type} purpose={purpose} /> + <TitleAndByline title={view.displayName} creator={view.creator} /> + <SaveButton view={view} pin /> </Header> <Description description={view.description} /> - {type === 'feed' && <Likes count={view.likeCount || 0} />} + <Likes count={view.likeCount || 0} /> </Outer> </Link> ) } export function Link({ - type, view, - label, children, + ...props }: Props & Omit<LinkProps, 'to'>) { const queryClient = useQueryClient() @@ -79,17 +65,12 @@ export function Link({ return createProfileFeedHref({feed: view}) }, [view]) + React.useEffect(() => { + precacheFeedFromGeneratorView(queryClient, view) + }, [view, queryClient]) + return ( - <InternalLink - to={href} - label={label} - onPress={() => { - if (type === 'feed') { - precacheFeedFromGeneratorView(queryClient, view) - } else { - precacheList(queryClient, view) - } - }}> + <InternalLink to={href} {...props}> {children} </InternalLink> ) @@ -132,13 +113,9 @@ export function AvatarPlaceholder({size = 40}: Omit<AvatarProps, 'src'>) { export function TitleAndByline({ title, creator, - type, - purpose, }: { title: string creator?: AppBskyActorDefs.ProfileViewBasic - type: 'feed' | 'list' - purpose?: AppBskyGraphDefs.ListView['purpose'] }) { const t = useTheme() @@ -151,15 +128,7 @@ export function TitleAndByline({ <Text style={[a.leading_snug, t.atoms.text_contrast_medium]} numberOfLines={1}> - {type === 'list' && purpose === 'app.bsky.graph.defs#curatelist' ? ( - <Trans>List by {sanitizeHandle(creator.handle, '@')}</Trans> - ) : type === 'list' && purpose === 'app.bsky.graph.defs#modlist' ? ( - <Trans> - Moderation list by {sanitizeHandle(creator.handle, '@')} - </Trans> - ) : ( - <Trans>Feed by {sanitizeHandle(creator.handle, '@')}</Trans> - )} + <Trans>Feed by {sanitizeHandle(creator.handle, '@')}</Trans> </Text> )} </View> @@ -221,34 +190,24 @@ export function Likes({count}: {count: number}) { ) } -export function Action({ - uri, +export function SaveButton({ + view, pin, - type, - purpose, }: { - uri: string + view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView pin?: boolean - type: 'feed' | 'list' - purpose?: AppBskyGraphDefs.ListView['purpose'] }) { const {hasSession} = useSession() - if ( - !hasSession || - (type === 'list' && purpose !== 'app.bsky.graph.defs#curatelist') - ) - return null - return <ActionInner uri={uri} pin={pin} type={type} /> + if (!hasSession) return null + return <SaveButtonInner view={view} pin={pin} /> } -function ActionInner({ - uri, +function SaveButtonInner({ + view, pin, - type, }: { - uri: string + view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView pin?: boolean - type: 'feed' | 'list' }) { const {_} = useLingui() const {data: preferences} = usePreferencesQuery() @@ -256,6 +215,10 @@ function ActionInner({ useAddSavedFeedsMutation() const {isPending: isRemovePending, mutateAsync: removeFeed} = useRemoveFeedMutation() + + const uri = view.uri + const type = view.uri.includes('app.bsky.feed.generator') ? 'feed' : 'list' + const savedFeedConfig = React.useMemo(() => { return preferences?.savedFeeds?.find(feed => feed.value === uri) }, [preferences?.savedFeeds, uri]) @@ -332,12 +295,9 @@ function ActionInner({ export function createProfileFeedHref({ feed, }: { - feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView + feed: AppBskyFeedDefs.GeneratorView }) { const urip = new AtUri(feed.uri) - const type = urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'list' const handleOrDid = feed.creator.handle || feed.creator.did - return `/profile/${handleOrDid}/${type === 'feed' ? 'feed' : 'lists'}/${ - urip.rkey - }` + return `/profile/${handleOrDid}/feed/${urip.rkey}` } diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx new file mode 100644 index 000000000..c0e0d0e25 --- /dev/null +++ b/src/components/ListCard.tsx @@ -0,0 +1,129 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' +import {Trans} from '@lingui/macro' +import {useQueryClient} from '@tanstack/react-query' + +import {sanitizeHandle} from 'lib/strings/handles' +import {precacheList} from 'state/queries/feed' +import {useTheme} from '#/alf' +import {atoms as a} from '#/alf' +import { + Avatar, + Description, + Header, + Outer, + SaveButton, +} from '#/components/FeedCard' +import {Link as InternalLink, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' + +/* + * This component is based on `FeedCard` and is tightly coupled with that + * component. Please refer to `FeedCard` for more context. + */ + +export { + Avatar, + AvatarPlaceholder, + Description, + Header, + Outer, + SaveButton, + TitleAndBylinePlaceholder, +} from '#/components/FeedCard' + +const CURATELIST = 'app.bsky.graph.defs#curatelist' +const MODLIST = 'app.bsky.graph.defs#modlist' + +type Props = { + view: AppBskyGraphDefs.ListView + showPinButton?: boolean +} + +export function Default(props: Props) { + const {view, showPinButton} = props + return ( + <Link label={view.name} {...props}> + <Outer> + <Header> + <Avatar src={view.avatar} /> + <TitleAndByline + title={view.name} + creator={view.creator} + purpose={view.purpose} + /> + {showPinButton && view.purpose === CURATELIST && ( + <SaveButton view={view} pin /> + )} + </Header> + <Description description={view.description} /> + </Outer> + </Link> + ) +} + +export function Link({ + view, + children, + ...props +}: Props & Omit<LinkProps, 'to'>) { + const queryClient = useQueryClient() + + const href = React.useMemo(() => { + return createProfileListHref({list: view}) + }, [view]) + + React.useEffect(() => { + precacheList(queryClient, view) + }, [view, queryClient]) + + return ( + <InternalLink to={href} {...props}> + {children} + </InternalLink> + ) +} + +export function TitleAndByline({ + title, + creator, + purpose = CURATELIST, +}: { + title: string + creator?: AppBskyActorDefs.ProfileViewBasic + purpose?: AppBskyGraphDefs.ListView['purpose'] +}) { + const t = useTheme() + + return ( + <View style={[a.flex_1]}> + <Text style={[a.text_md, a.font_bold, a.leading_snug]} numberOfLines={1}> + {title} + </Text> + {creator && ( + <Text + style={[a.leading_snug, t.atoms.text_contrast_medium]} + numberOfLines={1}> + {purpose === MODLIST ? ( + <Trans> + Moderation list by {sanitizeHandle(creator.handle, '@')} + </Trans> + ) : ( + <Trans>List by {sanitizeHandle(creator.handle, '@')}</Trans> + )} + </Text> + )} + </View> + ) +} + +export function createProfileListHref({ + list, +}: { + list: AppBskyGraphDefs.ListView +}) { + const urip = new AtUri(list.uri) + const handleOrDid = list.creator.handle || list.creator.did + return `/profile/${handleOrDid}/lists/${urip.rkey}` +} diff --git a/src/components/StarterPack/Main/FeedsList.tsx b/src/components/StarterPack/Main/FeedsList.tsx index e350a422c..7d7cd2047 100644 --- a/src/components/StarterPack/Main/FeedsList.tsx +++ b/src/components/StarterPack/Main/FeedsList.tsx @@ -45,7 +45,7 @@ export const FeedsList = React.forwardRef<SectionRef, ProfilesListProps>( (isWeb || index !== 0) && a.border_t, t.atoms.border_contrast_low, ]}> - <FeedCard.Default type="feed" view={item} /> + <FeedCard.Default view={item} /> </View> ) } |