From 7d3fe2b4c3cd9511df8f8026890c71b2119719f3 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 29 Apr 2024 11:55:41 +0200 Subject: [PATCH] Add loading indicator and empty result message to advanced interface search (#30085) --- .../compose/components/search_results.jsx | 112 +++++++++--------- .../containers/search_results_container.js | 20 ---- .../mastodon/features/compose/index.jsx | 4 +- app/javascript/mastodon/reducers/search.js | 1 + 4 files changed, 62 insertions(+), 75 deletions(-) delete mode 100644 app/javascript/mastodon/features/compose/containers/search_results_container.js diff --git a/app/javascript/mastodon/features/compose/components/search_results.jsx b/app/javascript/mastodon/features/compose/components/search_results.jsx index 667662781e0..6a482c8ec27 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.jsx +++ b/app/javascript/mastodon/features/compose/components/search_results.jsx @@ -1,16 +1,16 @@ -import PropTypes from 'prop-types'; +import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import TagIcon from '@/material-icons/400-24px/tag.svg?react'; +import { expandSearch } from 'mastodon/actions/search'; import { Icon } from 'mastodon/components/icon'; import { LoadMore } from 'mastodon/components/load_more'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { SearchSection } from 'mastodon/features/explore/components/search_section'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { ImmutableHashtag as Hashtag } from '../../../components/hashtag'; import AccountContainer from '../../../containers/account_container'; @@ -26,62 +26,68 @@ const withoutLastResult = list => { } }; -class SearchResults extends ImmutablePureComponent { +export const SearchResults = () => { + const results = useAppSelector((state) => state.getIn(['search', 'results'])); + const isLoading = useAppSelector((state) => state.getIn(['search', 'isLoading'])); - static propTypes = { - results: ImmutablePropTypes.map.isRequired, - expandSearch: PropTypes.func.isRequired, - searchTerm: PropTypes.string, - }; + const dispatch = useAppDispatch(); - handleLoadMoreAccounts = () => this.props.expandSearch('accounts'); + const handleLoadMoreAccounts = useCallback(() => { + dispatch(expandSearch('accounts')); + }, [dispatch]); - handleLoadMoreStatuses = () => this.props.expandSearch('statuses'); + const handleLoadMoreStatuses = useCallback(() => { + dispatch(expandSearch('statuses')); + }, [dispatch]); - handleLoadMoreHashtags = () => this.props.expandSearch('hashtags'); + const handleLoadMoreHashtags = useCallback(() => { + dispatch(expandSearch('hashtags')); + }, [dispatch]); - render () { - const { results } = this.props; + let accounts, statuses, hashtags; - let accounts, statuses, hashtags; - - if (results.get('accounts') && results.get('accounts').size > 0) { - accounts = ( - }> - {withoutLastResult(results.get('accounts')).map(accountId => )} - {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - hashtags = ( - }> - {withoutLastResult(results.get('hashtags')).map(hashtag => )} - {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - statuses = ( - }> - {withoutLastResult(results.get('statuses')).map(statusId => )} - {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - - return ( -
- {accounts} - {hashtags} - {statuses} -
+ if (results.get('accounts') && results.get('accounts').size > 0) { + accounts = ( + }> + {withoutLastResult(results.get('accounts')).map(accountId => )} + {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && } + ); } -} + if (results.get('hashtags') && results.get('hashtags').size > 0) { + hashtags = ( + }> + {withoutLastResult(results.get('hashtags')).map(hashtag => )} + {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && } + + ); + } -export default SearchResults; + if (results.get('statuses') && results.get('statuses').size > 0) { + statuses = ( + }> + {withoutLastResult(results.get('statuses')).map(statusId => )} + {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && } + + ); + } + + return ( +
+ {!accounts && !hashtags && !statuses && ( + isLoading ? ( + + ) : ( +
+ +
+ ) + )} + {accounts} + {hashtags} + {statuses} +
+ ); + +}; diff --git a/app/javascript/mastodon/features/compose/containers/search_results_container.js b/app/javascript/mastodon/features/compose/containers/search_results_container.js deleted file mode 100644 index 54c2af31776..00000000000 --- a/app/javascript/mastodon/features/compose/containers/search_results_container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux'; - -import { expandSearch } from 'mastodon/actions/search'; -import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions'; - -import SearchResults from '../components/search_results'; - -const mapStateToProps = state => ({ - results: state.getIn(['search', 'results']), - suggestions: state.getIn(['suggestions', 'items']), - searchTerm: state.getIn(['search', 'searchTerm']), -}); - -const mapDispatchToProps = dispatch => ({ - fetchSuggestions: () => dispatch(fetchSuggestions()), - expandSearch: type => dispatch(expandSearch(type)), - dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchResults); diff --git a/app/javascript/mastodon/features/compose/index.jsx b/app/javascript/mastodon/features/compose/index.jsx index ce8eb9e05dd..83c741fd191 100644 --- a/app/javascript/mastodon/features/compose/index.jsx +++ b/app/javascript/mastodon/features/compose/index.jsx @@ -29,9 +29,9 @@ import { mascot } from '../../initial_state'; import { isMobile } from '../../is_mobile'; import Motion from '../ui/util/optional_motion'; +import { SearchResults } from './components/search_results'; import ComposeFormContainer from './containers/compose_form_container'; import SearchContainer from './containers/search_container'; -import SearchResultsContainer from './containers/search_results_container'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -138,7 +138,7 @@ class Compose extends PureComponent { {({ x }) => (
- +
)}
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 72835eb9174..7828d49eeed 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -50,6 +50,7 @@ export default function search(state = initialState, action) { return state.set('hidden', true); case SEARCH_FETCH_REQUEST: return state.withMutations(map => { + map.set('results', ImmutableMap()); map.set('isLoading', true); map.set('submitted', true); map.set('type', action.searchType);