import PropTypes from 'prop-types'; import { useEffect, useCallback, useRef, useState } from 'react'; import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { useDispatch, useSelector } from 'react-redux'; import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import { followAccount, unfollowAccount } from 'mastodon/actions/accounts'; import { changeSetting } from 'mastodon/actions/settings'; import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" }, }); const Source = ({ id }) => { let label; switch (id) { case 'friends_of_friends': case 'similar_to_recently_followed': label = ; break; case 'featured': label = ; break; case 'most_followed': case 'most_interactions': label = ; break; } return (
{label}
); }; Source.propTypes = { id: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']), }; const Card = ({ id, sources }) => { const intl = useIntl(); const account = useSelector(state => state.getIn(['accounts', id])); const relationship = useSelector(state => state.getIn(['relationships', id])); const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at')); const dispatch = useDispatch(); const following = relationship?.get('following') ?? relationship?.get('requested'); const handleFollow = useCallback(() => { if (following) { dispatch(unfollowAccount(id)); } else { dispatch(followAccount(id)); } }, [id, following, dispatch]); const handleDismiss = useCallback(() => { dispatch(dismissSuggestion(id)); }, [id, dispatch]); return (
{firstVerifiedField ? : }
); }; Card.propTypes = { id: PropTypes.string.isRequired, sources: ImmutablePropTypes.list, }; const DISMISSIBLE_ID = 'home/follow-suggestions'; export const InlineFollowSuggestions = ({ hidden }) => { const intl = useIntl(); const dispatch = useDispatch(); const suggestions = useSelector(state => state.getIn(['suggestions', 'items'])); const isLoading = useSelector(state => state.getIn(['suggestions', 'isLoading'])); const dismissed = useSelector(state => state.getIn(['settings', 'dismissed_banners', DISMISSIBLE_ID])); const bodyRef = useRef(); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(true); useEffect(() => { dispatch(fetchSuggestions()); }, [dispatch]); useEffect(() => { if (!bodyRef.current) { return; } setCanScrollLeft(bodyRef.current.scrollLeft > 0); setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth); }, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]); const handleLeftNav = useCallback(() => { bodyRef.current.scrollLeft -= 200; }, [bodyRef]); const handleRightNav = useCallback(() => { bodyRef.current.scrollLeft += 200; }, [bodyRef]); const handleScroll = useCallback(() => { if (!bodyRef.current) { return; } setCanScrollLeft(bodyRef.current.scrollLeft > 0); setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth); }, [setCanScrollRight, setCanScrollLeft, bodyRef]); const handleDismiss = useCallback(() => { dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true)); }, [dispatch]); if (dismissed || (!isLoading && suggestions.isEmpty())) { return null; } if (hidden) { return (
); } return (

{suggestions.map(suggestion => ( ))}
{canScrollLeft && ( )} {canScrollRight && ( )}
); }; InlineFollowSuggestions.propTypes = { hidden: PropTypes.bool, };