2017-04-21 14:05:35 -04:00
import PropTypes from 'prop-types' ;
2023-05-23 11:15:17 -04:00
import { FormattedMessage } from 'react-intl' ;
2016-11-13 14:42:54 -05:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2023-05-23 11:15:17 -04:00
import ImmutablePureComponent from 'react-immutable-pure-component' ;
import { connect } from 'react-redux' ;
2018-08-26 10:39:37 -04:00
import { debounce } from 'lodash' ;
2023-05-23 11:15:17 -04:00
import { TimelineHint } from 'mastodon/components/timeline_hint' ;
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error' ;
import { normalizeForLookup } from 'mastodon/reducers/accounts_map' ;
import { getAccountHidden } from 'mastodon/selectors' ;
2024-08-21 03:08:58 -04:00
import { useAppSelector } from 'mastodon/store' ;
2023-05-23 11:15:17 -04:00
2016-11-13 14:42:54 -05:00
import {
2021-09-25 23:46:13 -04:00
lookupAccount ,
2021-09-27 01:23:48 -04:00
fetchAccount ,
2016-11-13 14:42:54 -05:00
fetchFollowers ,
2017-05-20 11:31:47 -04:00
expandFollowers ,
2016-11-13 14:42:54 -05:00
} from '../../actions/accounts' ;
2023-10-26 07:00:10 -04:00
import { ColumnBackButton } from '../../components/column_back_button' ;
2023-06-13 13:26:25 -04:00
import { LoadingIndicator } from '../../components/loading_indicator' ;
2018-08-26 10:39:37 -04:00
import ScrollableList from '../../components/scrollable_list' ;
2023-05-23 11:15:17 -04:00
import AccountContainer from '../../containers/account_container' ;
2023-11-03 11:00:03 -04:00
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint' ;
2023-05-23 11:15:17 -04:00
import HeaderContainer from '../account_timeline/containers/header_container' ;
import Column from '../ui/components/column' ;
2016-10-27 15:59:56 -04:00
2021-09-27 01:23:48 -04:00
const mapStateToProps = ( state , { params : { acct , id } } ) => {
2022-10-23 17:38:08 -04:00
const accountId = id || state . getIn ( [ 'accounts_map' , normalizeForLookup ( acct ) ] ) ;
2021-09-25 23:46:13 -04:00
if ( ! accountId ) {
return {
isLoading : true ,
} ;
}
return {
accountId ,
remote : ! ! ( state . getIn ( [ 'accounts' , accountId , 'acct' ] ) !== state . getIn ( [ 'accounts' , accountId , 'username' ] ) ) ,
remoteUrl : state . getIn ( [ 'accounts' , accountId , 'url' ] ) ,
isAccount : ! ! state . getIn ( [ 'accounts' , accountId ] ) ,
accountIds : state . getIn ( [ 'user_lists' , 'followers' , accountId , 'items' ] ) ,
hasMore : ! ! state . getIn ( [ 'user_lists' , 'followers' , accountId , 'next' ] ) ,
isLoading : state . getIn ( [ 'user_lists' , 'followers' , accountId , 'isLoading' ] , true ) ,
2022-05-10 03:44:35 -04:00
suspended : state . getIn ( [ 'accounts' , accountId , 'suspended' ] , false ) ,
2023-11-09 07:58:02 -05:00
hideCollections : state . getIn ( [ 'accounts' , accountId , 'hide_collections' ] , false ) ,
2022-05-10 03:44:35 -04:00
hidden : getAccountHidden ( state , accountId ) ,
2021-09-25 23:46:13 -04:00
blockedBy : state . getIn ( [ 'relationships' , accountId , 'blocked_by' ] , false ) ,
} ;
} ;
2016-10-27 15:59:56 -04:00
2024-08-21 03:08:58 -04:00
const RemoteHint = ( { accountId , url } ) => {
const acct = useAppSelector ( state => state . accounts . get ( accountId ) ? . acct ) ;
const domain = acct ? acct . split ( '@' ) [ 1 ] : undefined ;
return (
< TimelineHint
url = { url }
message = { < FormattedMessage id = 'hints.profiles.followers_may_be_missing' defaultMessage = 'Followers for this profile may be missing.' / > }
label = { < FormattedMessage id = 'hints.profiles.see_more_followers' defaultMessage = 'See more followers on {domain}' values = { { domain : < strong > { domain } < / strong > } } / > }
/ >
) ;
} ;
2020-06-14 16:29:40 -04:00
RemoteHint . propTypes = {
url : PropTypes . string . isRequired ,
2024-08-21 03:08:58 -04:00
accountId : PropTypes . string . isRequired ,
2020-06-14 16:29:40 -04:00
} ;
2018-09-14 11:59:48 -04:00
class Followers extends ImmutablePureComponent {
2016-10-27 15:59:56 -04:00
2017-05-12 08:44:10 -04:00
static propTypes = {
2021-09-25 23:46:13 -04:00
params : PropTypes . shape ( {
2021-09-27 01:23:48 -04:00
acct : PropTypes . string ,
id : PropTypes . string ,
2021-09-25 23:46:13 -04:00
} ) . isRequired ,
accountId : PropTypes . string ,
2017-05-12 08:44:10 -04:00
dispatch : PropTypes . func . isRequired ,
2017-05-20 11:31:47 -04:00
accountIds : ImmutablePropTypes . list ,
2017-06-05 12:18:56 -04:00
hasMore : PropTypes . bool ,
2020-04-12 07:38:00 -04:00
isLoading : PropTypes . bool ,
2019-04-06 22:59:13 -04:00
blockedBy : PropTypes . bool ,
2019-04-08 23:02:48 -04:00
isAccount : PropTypes . bool ,
2022-05-10 03:44:35 -04:00
suspended : PropTypes . bool ,
hidden : PropTypes . bool ,
2020-06-14 16:29:40 -04:00
remote : PropTypes . bool ,
remoteUrl : PropTypes . string ,
2019-07-19 03:25:22 -04:00
multiColumn : PropTypes . bool ,
2017-05-12 08:44:10 -04:00
} ;
2016-10-27 15:59:56 -04:00
2021-09-25 23:46:13 -04:00
_load ( ) {
2021-09-27 01:23:48 -04:00
const { accountId , isAccount , dispatch } = this . props ;
2021-09-25 23:46:13 -04:00
2021-09-27 01:23:48 -04:00
if ( ! isAccount ) dispatch ( fetchAccount ( accountId ) ) ;
2021-09-25 23:46:13 -04:00
dispatch ( fetchFollowers ( accountId ) ) ;
}
componentDidMount ( ) {
const { params : { acct } , accountId , dispatch } = this . props ;
if ( accountId ) {
this . _load ( ) ;
} else {
dispatch ( lookupAccount ( acct ) ) ;
2019-09-29 10:27:00 -04:00
}
2017-04-21 14:05:35 -04:00
}
2016-10-27 15:59:56 -04:00
2021-09-25 23:46:13 -04:00
componentDidUpdate ( prevProps ) {
const { params : { acct } , accountId , dispatch } = this . props ;
if ( prevProps . accountId !== accountId && accountId ) {
this . _load ( ) ;
} else if ( prevProps . params . acct !== acct ) {
dispatch ( lookupAccount ( acct ) ) ;
2016-10-27 15:59:56 -04:00
}
2017-04-21 14:05:35 -04:00
}
2016-10-27 15:59:56 -04:00
2018-08-26 10:39:37 -04:00
handleLoadMore = debounce ( ( ) => {
2021-09-25 23:46:13 -04:00
this . props . dispatch ( expandFollowers ( this . props . accountId ) ) ;
2018-08-26 10:39:37 -04:00
} , 300 , { leading : true } ) ;
2017-01-30 15:40:55 -05:00
2016-10-27 15:59:56 -04:00
render ( ) {
2023-11-09 07:58:02 -05:00
const { accountId , accountIds , hasMore , blockedBy , isAccount , multiColumn , isLoading , suspended , hidden , remote , remoteUrl , hideCollections } = this . props ;
2019-04-08 23:02:48 -04:00
if ( ! isAccount ) {
return (
2023-04-12 06:44:58 -04:00
< BundleColumnError multiColumn = { multiColumn } errorType = 'routing' / >
2019-04-08 23:02:48 -04:00
) ;
}
2017-06-05 08:13:20 -04:00
2016-10-27 15:59:56 -04:00
if ( ! accountIds ) {
2017-01-30 15:40:55 -05:00
return (
< Column >
< LoadingIndicator / >
< / Column >
) ;
2016-10-27 15:59:56 -04:00
}
2020-06-14 16:29:40 -04:00
let emptyMessage ;
2022-05-10 03:44:35 -04:00
const forceEmptyState = blockedBy || suspended || hidden ;
if ( suspended ) {
emptyMessage = < FormattedMessage id = 'empty_column.account_suspended' defaultMessage = 'Account suspended' / > ;
} else if ( hidden ) {
emptyMessage = < LimitedAccountHint accountId = { accountId } / > ;
} else if ( blockedBy ) {
2020-06-14 16:29:40 -04:00
emptyMessage = < FormattedMessage id = 'empty_column.account_unavailable' defaultMessage = 'Profile unavailable' / > ;
2023-11-09 07:58:02 -05:00
} else if ( hideCollections && accountIds . isEmpty ( ) ) {
emptyMessage = < FormattedMessage id = 'empty_column.account_hides_collections' defaultMessage = 'This user has chosen to not make this information available' / > ;
2020-06-14 16:29:40 -04:00
} else if ( remote && accountIds . isEmpty ( ) ) {
2024-08-21 03:08:58 -04:00
emptyMessage = < RemoteHint accountId = { accountId } url = { remoteUrl } / > ;
2020-06-14 16:29:40 -04:00
} else {
emptyMessage = < FormattedMessage id = 'account.followers.empty' defaultMessage = 'No one follows this user yet.' / > ;
}
2024-08-21 03:08:58 -04:00
const remoteMessage = remote ? < RemoteHint accountId = { accountId } url = { remoteUrl } / > : null ;
2017-06-05 08:13:20 -04:00
2016-10-27 15:59:56 -04:00
return (
2017-01-30 15:40:55 -05:00
< Column >
2023-10-26 07:00:10 -04:00
< ColumnBackButton / >
2017-01-31 16:34:33 -05:00
2018-08-26 10:39:37 -04:00
< ScrollableList
scrollKey = 'followers'
2022-05-10 03:44:35 -04:00
hasMore = { ! forceEmptyState && hasMore }
2020-04-12 07:38:00 -04:00
isLoading = { isLoading }
2018-08-26 10:39:37 -04:00
onLoadMore = { this . handleLoadMore }
2021-09-25 23:46:13 -04:00
prepend = { < HeaderContainer accountId = { this . props . accountId } hideTabs / > }
2018-08-28 19:19:58 -04:00
alwaysPrepend
2020-06-14 16:29:40 -04:00
append = { remoteMessage }
2018-08-26 10:39:37 -04:00
emptyMessage = { emptyMessage }
2019-07-19 03:25:22 -04:00
bindToDocument = { ! multiColumn }
2018-08-26 10:39:37 -04:00
>
2022-05-10 03:44:35 -04:00
{ forceEmptyState ? [ ] : accountIds . map ( id =>
2020-03-08 11:02:36 -04:00
< AccountContainer key = { id } id = { id } withNote = { false } / > ,
2018-08-26 10:39:37 -04:00
) }
< / ScrollableList >
2017-01-30 15:40:55 -05:00
< / Column >
2016-10-27 15:59:56 -04:00
) ;
}
2017-04-21 14:05:35 -04:00
}
2023-03-23 22:17:53 -04:00
export default connect ( mapStateToProps ) ( Followers ) ;