From aca2df0775b0a82959db3f73bdb00d4145ee9f99 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 10 Sep 2022 10:46:12 -0500 Subject: [PATCH 1/3] Favourites: quick & dirty conversion to TSX --- .../features/favourited_statuses/index.js | 158 ------------------ .../features/favourited_statuses/index.tsx | 151 +++++++++++++++++ 2 files changed, 151 insertions(+), 158 deletions(-) delete mode 100644 app/soapbox/features/favourited_statuses/index.js create mode 100644 app/soapbox/features/favourited_statuses/index.tsx diff --git a/app/soapbox/features/favourited_statuses/index.js b/app/soapbox/features/favourited_statuses/index.js deleted file mode 100644 index b4ffb6a8a..000000000 --- a/app/soapbox/features/favourited_statuses/index.js +++ /dev/null @@ -1,158 +0,0 @@ -import debounce from 'lodash/debounce'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; - -import { fetchAccount, fetchAccountByUsername } from 'soapbox/actions/accounts'; -import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from 'soapbox/actions/favourites'; -import MissingIndicator from 'soapbox/components/missing_indicator'; -import StatusList from 'soapbox/components/status_list'; -import { Spinner } from 'soapbox/components/ui'; -import { findAccountByUsername } from 'soapbox/selectors'; -import { getFeatures } from 'soapbox/utils/features'; - -import Column from '../ui/components/column'; - -const messages = defineMessages({ - heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, -}); - -const mapStateToProps = (state, { params }) => { - const username = params.username || ''; - const me = state.get('me'); - const meUsername = state.getIn(['accounts', me, 'username'], ''); - - const isMyAccount = (username.toLowerCase() === meUsername.toLowerCase()); - - const features = getFeatures(state.get('instance')); - - if (isMyAccount) { - return { - isMyAccount, - statusIds: state.status_lists.get('favourites').items, - isLoading: state.status_lists.get('favourites').isLoading, - hasMore: !!state.status_lists.get('favourites').next, - }; - } - - const accountFetchError = ((state.getIn(['accounts', -1, 'username']) || '').toLowerCase() === username.toLowerCase()); - - let accountId = -1; - if (accountFetchError) { - accountId = null; - } else { - const account = findAccountByUsername(state, username); - accountId = account ? account.getIn(['id'], null) : -1; - } - - const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false); - const unavailable = (me === accountId) ? false : (isBlocked && !features.blockersVisible); - - return { - isMyAccount, - accountId, - unavailable, - username, - isAccount: !!state.getIn(['accounts', accountId]), - statusIds: state.status_lists.get(`favourites:${accountId}`)?.items || [], - isLoading: state.status_lists.get(`favourites:${accountId}`)?.isLoading, - hasMore: !!state.status_lists.get(`favourites:${accountId}`)?.next, - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class Favourites extends ImmutablePureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.orderedSet.isRequired, - intl: PropTypes.object.isRequired, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - isMyAccount: PropTypes.bool.isRequired, - }; - - componentDidMount() { - const { accountId, isMyAccount, username } = this.props; - - if (isMyAccount) - this.props.dispatch(fetchFavouritedStatuses()); - else { - if (accountId && accountId !== -1) { - this.props.dispatch(fetchAccount(accountId)); - this.props.dispatch(fetchAccountFavouritedStatuses(accountId)); - } else { - this.props.dispatch(fetchAccountByUsername(username)); - } - } - } - - componentDidUpdate(prevProps) { - const { accountId, isMyAccount } = this.props; - - if (!isMyAccount && accountId && accountId !== -1 && (accountId !== prevProps.accountId && accountId)) { - this.props.dispatch(fetchAccount(accountId)); - this.props.dispatch(fetchAccountFavouritedStatuses(accountId)); - } - } - - handleLoadMore = debounce(() => { - const { accountId, isMyAccount } = this.props; - - if (isMyAccount) { - this.props.dispatch(expandFavouritedStatuses()); - } else { - this.props.dispatch(expandAccountFavouritedStatuses(accountId)); - } - }, 300, { leading: true }) - - render() { - const { intl, statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props; - - if (!isMyAccount && !isAccount && accountId !== -1) { - return ( - - ); - } - - if (accountId === -1) { - return ( - - - - ); - } - - if (unavailable) { - return ( - -
- -
-
- ); - } - - const emptyMessage = isMyAccount - ? - : ; - - return ( - - - - ); - } - -} diff --git a/app/soapbox/features/favourited_statuses/index.tsx b/app/soapbox/features/favourited_statuses/index.tsx new file mode 100644 index 000000000..ecd9a8045 --- /dev/null +++ b/app/soapbox/features/favourited_statuses/index.tsx @@ -0,0 +1,151 @@ +import { OrderedSet as ImmutableOrderedSet } from 'immutable'; +import debounce from 'lodash/debounce'; +import React, { useCallback, useEffect } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { fetchAccount, fetchAccountByUsername } from 'soapbox/actions/accounts'; +import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from 'soapbox/actions/favourites'; +import MissingIndicator from 'soapbox/components/missing_indicator'; +import StatusList from 'soapbox/components/status_list'; +import { Spinner } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { findAccountByUsername } from 'soapbox/selectors'; +import { getFeatures } from 'soapbox/utils/features'; + +import Column from '../ui/components/column'; + +import type { RootState } from 'soapbox/store'; + +const messages = defineMessages({ + heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, +}); + +const mapStateToProps = (state: RootState, { params }: IFavourites) => { + const username = params?.username || ''; + const me = state.get('me'); + const meUsername = state.accounts.get(me)?.username || ''; + + const isMyAccount = (username.toLowerCase() === meUsername?.toLowerCase()); + + const features = getFeatures(state.get('instance')); + + if (isMyAccount) { + return { + isMyAccount, + statusIds: state.status_lists.get('favourites')?.items || ImmutableOrderedSet(), + isLoading: state.status_lists.get('favourites')?.isLoading === true, + hasMore: !!state.status_lists.get('favourites')?.next, + unavailable: false, + isAccount: true, + }; + } + + const accountFetchError = ((state.accounts.get(-1)?.username || '').toLowerCase() === username.toLowerCase()); + + let accountId: number | string | null = -1; + if (accountFetchError) { + accountId = null; + } else { + const account = findAccountByUsername(state, username); + accountId = account?.id || -1; + } + + const isBlocked = state.relationships.getIn([accountId, 'blocked_by'], false) === true; + const unavailable = (me === accountId) ? false : (isBlocked && !features.blockersVisible); + + return { + isMyAccount, + accountId, + unavailable, + username, + isAccount: !!state.getIn(['accounts', accountId]), + statusIds: state.status_lists.get(`favourites:${accountId}`)?.items || ImmutableOrderedSet(), + isLoading: state.status_lists.get(`favourites:${accountId}`)?.isLoading === true, + hasMore: !!state.status_lists.get(`favourites:${accountId}`)?.next, + }; +}; + +interface IFavourites { + params?: { + username?: string, + } +} + +const Favourites: React.FC = (props) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const username = props.params?.username || ''; + const { statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = useAppSelector(state => mapStateToProps(state, props)); + + useEffect(() => { + if (isMyAccount) + dispatch(fetchFavouritedStatuses()); + else { + if (typeof accountId === 'string') { + dispatch(fetchAccount(accountId)); + dispatch(fetchAccountFavouritedStatuses(accountId)); + } else { + dispatch(fetchAccountByUsername(username)); + } + } + }, []); + + useEffect(() => { + if (!isMyAccount && typeof accountId === 'string') { + dispatch(fetchAccount(accountId)); + dispatch(fetchAccountFavouritedStatuses(accountId)); + } + }, [accountId]); + + const handleLoadMore = useCallback(debounce(() => { + if (isMyAccount) { + dispatch(expandFavouritedStatuses()); + } else if (typeof accountId === 'string') { + dispatch(expandAccountFavouritedStatuses(accountId)); + } + }, 300, { leading: true }), [accountId]); + + if (!isMyAccount && !isAccount && accountId !== -1) { + return ( + + ); + } + + if (accountId === -1) { + return ( + + + + ); + } + + if (unavailable) { + return ( + +
+ +
+
+ ); + } + + const emptyMessage = isMyAccount + ? + : ; + + return ( + + + + ); +}; + +export default Favourites; \ No newline at end of file From 0355d2a9275a92a8256989923d6d6906f37325e4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 10 Sep 2022 11:07:35 -0500 Subject: [PATCH 2/3] Favourites: refactor, clean up, make it sane --- .../features/favourited_statuses/index.tsx | 115 ++++++------------ 1 file changed, 36 insertions(+), 79 deletions(-) diff --git a/app/soapbox/features/favourited_statuses/index.tsx b/app/soapbox/features/favourited_statuses/index.tsx index ecd9a8045..2b720ab4c 100644 --- a/app/soapbox/features/favourited_statuses/index.tsx +++ b/app/soapbox/features/favourited_statuses/index.tsx @@ -7,84 +7,49 @@ import { fetchAccount, fetchAccountByUsername } from 'soapbox/actions/accounts'; import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from 'soapbox/actions/favourites'; import MissingIndicator from 'soapbox/components/missing_indicator'; import StatusList from 'soapbox/components/status_list'; -import { Spinner } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { findAccountByUsername } from 'soapbox/selectors'; -import { getFeatures } from 'soapbox/utils/features'; import Column from '../ui/components/column'; -import type { RootState } from 'soapbox/store'; - const messages = defineMessages({ heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' }, }); -const mapStateToProps = (state: RootState, { params }: IFavourites) => { - const username = params?.username || ''; - const me = state.get('me'); - const meUsername = state.accounts.get(me)?.username || ''; - - const isMyAccount = (username.toLowerCase() === meUsername?.toLowerCase()); - - const features = getFeatures(state.get('instance')); - - if (isMyAccount) { - return { - isMyAccount, - statusIds: state.status_lists.get('favourites')?.items || ImmutableOrderedSet(), - isLoading: state.status_lists.get('favourites')?.isLoading === true, - hasMore: !!state.status_lists.get('favourites')?.next, - unavailable: false, - isAccount: true, - }; - } - - const accountFetchError = ((state.accounts.get(-1)?.username || '').toLowerCase() === username.toLowerCase()); - - let accountId: number | string | null = -1; - if (accountFetchError) { - accountId = null; - } else { - const account = findAccountByUsername(state, username); - accountId = account?.id || -1; - } - - const isBlocked = state.relationships.getIn([accountId, 'blocked_by'], false) === true; - const unavailable = (me === accountId) ? false : (isBlocked && !features.blockersVisible); - - return { - isMyAccount, - accountId, - unavailable, - username, - isAccount: !!state.getIn(['accounts', accountId]), - statusIds: state.status_lists.get(`favourites:${accountId}`)?.items || ImmutableOrderedSet(), - isLoading: state.status_lists.get(`favourites:${accountId}`)?.isLoading === true, - hasMore: !!state.status_lists.get(`favourites:${accountId}`)?.next, - }; -}; - interface IFavourites { params?: { username?: string, } } +/** Timeline displaying a user's favourited statuses. */ const Favourites: React.FC = (props) => { const intl = useIntl(); const dispatch = useAppDispatch(); + const features = useFeatures(); + const ownAccount = useOwnAccount(); const username = props.params?.username || ''; - const { statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = useAppSelector(state => mapStateToProps(state, props)); + const account = useAppSelector(state => findAccountByUsername(state, username)); + const isOwnAccount = username.toLowerCase() === ownAccount?.username?.toLowerCase(); + + const timelineKey = isOwnAccount ? 'favourites' : `favourites:${account?.id}`; + const statusIds = useAppSelector(state => state.status_lists.get(timelineKey)?.items || ImmutableOrderedSet()); + const isLoading = useAppSelector(state => state.status_lists.get(timelineKey)?.isLoading === true); + const hasMore = useAppSelector(state => !!state.status_lists.get(timelineKey)?.next); + + const unavailable = useAppSelector(state => { + const blockedBy = state.relationships.getIn([account?.id, 'blocked_by']) === true; + return isOwnAccount ? false : (blockedBy && !features.blockersVisible); + }); useEffect(() => { - if (isMyAccount) + if (isOwnAccount) dispatch(fetchFavouritedStatuses()); else { - if (typeof accountId === 'string') { - dispatch(fetchAccount(accountId)); - dispatch(fetchAccountFavouritedStatuses(accountId)); + if (account) { + dispatch(fetchAccount(account.id)); + dispatch(fetchAccountFavouritedStatuses(account.id)); } else { dispatch(fetchAccountByUsername(username)); } @@ -92,33 +57,19 @@ const Favourites: React.FC = (props) => { }, []); useEffect(() => { - if (!isMyAccount && typeof accountId === 'string') { - dispatch(fetchAccount(accountId)); - dispatch(fetchAccountFavouritedStatuses(accountId)); + if (account && !isOwnAccount) { + dispatch(fetchAccount(account.id)); + dispatch(fetchAccountFavouritedStatuses(account.id)); } - }, [accountId]); + }, [account?.id]); const handleLoadMore = useCallback(debounce(() => { - if (isMyAccount) { + if (isOwnAccount) { dispatch(expandFavouritedStatuses()); - } else if (typeof accountId === 'string') { - dispatch(expandAccountFavouritedStatuses(accountId)); + } else if (account) { + dispatch(expandAccountFavouritedStatuses(account.id)); } - }, 300, { leading: true }), [accountId]); - - if (!isMyAccount && !isAccount && accountId !== -1) { - return ( - - ); - } - - if (accountId === -1) { - return ( - - - - ); - } + }, 300, { leading: true }), [account?.id]); if (unavailable) { return ( @@ -130,7 +81,13 @@ const Favourites: React.FC = (props) => { ); } - const emptyMessage = isMyAccount + if (!account) { + return ( + + ); + } + + const emptyMessage = isOwnAccount ? : ; @@ -140,7 +97,7 @@ const Favourites: React.FC = (props) => { statusIds={statusIds} scrollKey='favourited_statuses' hasMore={hasMore} - isLoading={typeof isLoading === 'boolean' ? isLoading : true} + isLoading={isLoading} onLoadMore={handleLoadMore} emptyMessage={emptyMessage} /> From 74d2325b582cba218f7ccaea527a816286bf108e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Sep 2022 10:14:57 -0500 Subject: [PATCH 3/3] Favourites: unavailable --> isUnavailable, move useEffect's down --- .../features/favourited_statuses/index.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/soapbox/features/favourited_statuses/index.tsx b/app/soapbox/features/favourited_statuses/index.tsx index 2b720ab4c..1a8ba3bfa 100644 --- a/app/soapbox/features/favourited_statuses/index.tsx +++ b/app/soapbox/features/favourited_statuses/index.tsx @@ -38,11 +38,19 @@ const Favourites: React.FC = (props) => { const isLoading = useAppSelector(state => state.status_lists.get(timelineKey)?.isLoading === true); const hasMore = useAppSelector(state => !!state.status_lists.get(timelineKey)?.next); - const unavailable = useAppSelector(state => { + const isUnavailable = useAppSelector(state => { const blockedBy = state.relationships.getIn([account?.id, 'blocked_by']) === true; return isOwnAccount ? false : (blockedBy && !features.blockersVisible); }); + const handleLoadMore = useCallback(debounce(() => { + if (isOwnAccount) { + dispatch(expandFavouritedStatuses()); + } else if (account) { + dispatch(expandAccountFavouritedStatuses(account.id)); + } + }, 300, { leading: true }), [account?.id]); + useEffect(() => { if (isOwnAccount) dispatch(fetchFavouritedStatuses()); @@ -63,15 +71,7 @@ const Favourites: React.FC = (props) => { } }, [account?.id]); - const handleLoadMore = useCallback(debounce(() => { - if (isOwnAccount) { - dispatch(expandFavouritedStatuses()); - } else if (account) { - dispatch(expandAccountFavouritedStatuses(account.id)); - } - }, 300, { leading: true }), [account?.id]); - - if (unavailable) { + if (isUnavailable) { return (