Use ScrollableList for search results

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
merge-requests/821/head
marcin mikołajczak 3 years ago
parent dde588faa8
commit 742b1f2b58

@ -95,7 +95,7 @@ export const expandSearch = type => (dispatch, getState) => {
const value = getState().getIn(['search', 'value']); const value = getState().getIn(['search', 'value']);
const offset = getState().getIn(['search', 'results', type]).size; const offset = getState().getIn(['search', 'results', type]).size;
dispatch(expandSearchRequest()); dispatch(expandSearchRequest(type));
api(getState).get('/api/v2/search', { api(getState).get('/api/v2/search', {
params: { params: {
@ -119,8 +119,9 @@ export const expandSearch = type => (dispatch, getState) => {
}); });
}; };
export const expandSearchRequest = () => ({ export const expandSearchRequest = (searchType) => ({
type: SEARCH_EXPAND_REQUEST, type: SEARCH_EXPAND_REQUEST,
searchType,
}); });
export const expandSearchSuccess = (results, searchTerm, searchType) => ({ export const expandSearchSuccess = (results, searchTerm, searchType) => ({

@ -6,12 +6,13 @@ import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container'; import StatusContainer from '../../../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Hashtag from '../../../components/hashtag'; import Hashtag from '../../../components/hashtag';
import LoadingIndicator from 'soapbox/components/loading_indicator';
import FilterBar from '../../search/components/filter_bar'; import FilterBar from '../../search/components/filter_bar';
import LoadMore from '../../../components/load_more';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { WhoToFollowPanel } from 'soapbox/features/ui/util/async-components'; import { WhoToFollowPanel } from 'soapbox/features/ui/util/async-components';
import classNames from 'classnames'; import ScrollableList from 'soapbox/components/scrollable_list';
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
export default class SearchResults extends ImmutablePureComponent { export default class SearchResults extends ImmutablePureComponent {
@ -32,13 +33,7 @@ export default class SearchResults extends ImmutablePureComponent {
render() { render() {
const { value, results, submitted, selectedFilter, features } = this.props; const { value, results, submitted, selectedFilter, features } = this.props;
if (submitted && results.isEmpty()) { if (!submitted && features.suggestions && results.isEmpty()) {
return (
<div className='search-results'>
<LoadingIndicator />
</div>
);
} else if (features.suggestions && results.isEmpty()) {
return ( return (
<BundleContainer fetchComponent={WhoToFollowPanel}> <BundleContainer fetchComponent={WhoToFollowPanel}>
{Component => <Component limit={5} />} {Component => <Component limit={5} />}
@ -48,15 +43,19 @@ export default class SearchResults extends ImmutablePureComponent {
let searchResults; let searchResults;
let hasMore = false; let hasMore = false;
let loaded;
let noResultsMessage;
let placeholderComponent = PlaceholderStatus;
if (selectedFilter === 'accounts' && results.get('accounts')) { if (selectedFilter === 'accounts' && results.get('accounts')) {
hasMore = results.get('accountsHasMore'); hasMore = results.get('accountsHasMore');
loaded = results.get('accountsLoaded');
placeholderComponent = PlaceholderAccount;
searchResults = results.get('accounts').size > 0 ? ( if (results.get('accounts').size > 0) {
<div className={classNames('search-results__section', { 'has-more': hasMore })}> searchResults = results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />);
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} } else {
</div> noResultsMessage = (
) : (
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage <FormattedMessage
id='empty_column.search.accounts' id='empty_column.search.accounts'
@ -66,15 +65,16 @@ export default class SearchResults extends ImmutablePureComponent {
</div> </div>
); );
} }
}
if (selectedFilter === 'statuses' && results.get('statuses')) { if (selectedFilter === 'statuses' && results.get('statuses')) {
hasMore = results.get('statusesHasMore'); hasMore = results.get('statusesHasMore');
loaded = results.get('statusesLoaded');
searchResults = results.get('statuses').size > 0 ? ( if (results.get('statuses').size > 0) {
<div className={classNames('search-results__section', { 'has-more': hasMore })}> searchResults = results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />);
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} } else {
</div> noResultsMessage = (
) : (
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage <FormattedMessage
id='empty_column.search.statuses' id='empty_column.search.statuses'
@ -84,15 +84,17 @@ export default class SearchResults extends ImmutablePureComponent {
</div> </div>
); );
} }
}
if (selectedFilter === 'hashtags' && results.get('hashtags')) { if (selectedFilter === 'hashtags' && results.get('hashtags')) {
hasMore = results.get('hashtagsHasMore'); hasMore = results.get('hashtagsHasMore');
loaded = results.get('hashtagsLoaded');
placeholderComponent = PlaceholderHashtag;
searchResults = results.get('hashtags').size > 0 ? ( if (results.get('hashtags').size > 0) {
<div className={classNames('search-results__section', { 'has-more': hasMore })}> searchResults = results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />);
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)} } else {
</div> noResultsMessage = (
) : (
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage <FormattedMessage
id='empty_column.search.hashtags' id='empty_column.search.hashtags'
@ -102,14 +104,26 @@ export default class SearchResults extends ImmutablePureComponent {
</div> </div>
); );
} }
}
return ( return (
<> <>
{submitted && <FilterBar selectedFilter={submitted ? selectedFilter : null} selectFilter={this.handleSelectFilter} />} {submitted && <FilterBar selectedFilter={submitted ? selectedFilter : null} selectFilter={this.handleSelectFilter} />}
{noResultsMessage || (
<ScrollableList
key={selectedFilter}
scrollKey={`${selectedFilter}:${value}`}
isLoading={submitted && !loaded}
showLoading={submitted && !loaded && results.isEmpty()}
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
placeholderComponent={placeholderComponent}
placeholderCount={20}
>
{searchResults} {searchResults}
</ScrollableList>
{hasMore && <LoadMore visible onClick={this.handleLoadMore} />} )}
</> </>
); );
} }

@ -0,0 +1,20 @@
import React from 'react';
import { generateText, randomIntFromInterval } from '../utils';
export default class PlaceholderHashtag extends React.Component {
render() {
const length = randomIntFromInterval(15, 30);
return (
<div className='placeholder-hashtag'>
<div className='trends__item'>
<div className='trends__item__name'>
{generateText(length)}
</div>
</div>
</div>
);
}
}

@ -5,6 +5,7 @@ import {
SEARCH_FETCH_SUCCESS, SEARCH_FETCH_SUCCESS,
SEARCH_SHOW, SEARCH_SHOW,
SEARCH_FILTER_SET, SEARCH_FILTER_SET,
SEARCH_EXPAND_REQUEST,
SEARCH_EXPAND_SUCCESS, SEARCH_EXPAND_SUCCESS,
} from '../actions/search'; } from '../actions/search';
import { import {
@ -28,7 +29,6 @@ export default function search(state = initialState, action) {
case SEARCH_CHANGE: case SEARCH_CHANGE:
return state.withMutations(map => { return state.withMutations(map => {
map.set('value', action.value); map.set('value', action.value);
map.set('submitted', false);
}); });
case SEARCH_CLEAR: case SEARCH_CLEAR:
return state.withMutations(map => { return state.withMutations(map => {
@ -58,6 +58,9 @@ export default function search(state = initialState, action) {
accountsHasMore: action.results.accounts.length >= 20, accountsHasMore: action.results.accounts.length >= 20,
statusesHasMore: action.results.statuses.length >= 20, statusesHasMore: action.results.statuses.length >= 20,
hashtagsHasMore: action.results.hashtags.length >= 20, hashtagsHasMore: action.results.hashtags.length >= 20,
accountsLoaded: true,
statusesLoaded: true,
hashtagsLoaded: true,
})).set('submitted', true).set('filter', action.results.accounts.length > 0 })).set('submitted', true).set('filter', action.results.accounts.length > 0
? 'accounts' ? 'accounts'
: action.results.statuses.length > 0 : action.results.statuses.length > 0
@ -67,9 +70,12 @@ export default function search(state = initialState, action) {
: 'accounts'); : 'accounts');
case SEARCH_FILTER_SET: case SEARCH_FILTER_SET:
return state.set('filter', action.value); return state.set('filter', action.value);
case SEARCH_EXPAND_REQUEST:
return state.setIn(['results', `${action.searchType}Loaded`], false);
case SEARCH_EXPAND_SUCCESS: case SEARCH_EXPAND_SUCCESS:
return state.withMutations((state) => { return state.withMutations((state) => {
state.setIn(['results', `${action.searchType}HasMore`], action.results[action.searchType].length >= 20); state.setIn(['results', `${action.searchType}HasMore`], action.results[action.searchType].length >= 20);
state.setIn(['results', `${action.searchType}Loaded`], true);
state.updateIn(['results', action.searchType], list => list.concat(action.results[action.searchType].map(item => item.id))); state.updateIn(['results', action.searchType], list => list.concat(action.results[action.searchType].map(item => item.id)));
}); });
default: default:

@ -1,4 +1,5 @@
.placeholder-status, .placeholder-status,
.placeholder-hashtag,
.notification--placeholder { .notification--placeholder {
position: relative; position: relative;

Loading…
Cancel
Save