Use ScrollableList for search results

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
draftjs
marcin mikołajczak 2021-10-24 19:54:51 +02:00
rodzic dde588faa8
commit 742b1f2b58
5 zmienionych plików z 97 dodań i 55 usunięć

Wyświetl plik

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

Wyświetl plik

@ -6,12 +6,13 @@ import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Hashtag from '../../../components/hashtag';
import LoadingIndicator from 'soapbox/components/loading_indicator';
import FilterBar from '../../search/components/filter_bar';
import LoadMore from '../../../components/load_more';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
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 {
@ -32,13 +33,7 @@ export default class SearchResults extends ImmutablePureComponent {
render() {
const { value, results, submitted, selectedFilter, features } = this.props;
if (submitted && results.isEmpty()) {
return (
<div className='search-results'>
<LoadingIndicator />
</div>
);
} else if (features.suggestions && results.isEmpty()) {
if (!submitted && features.suggestions && results.isEmpty()) {
return (
<BundleContainer fetchComponent={WhoToFollowPanel}>
{Component => <Component limit={5} />}
@ -48,68 +43,87 @@ export default class SearchResults extends ImmutablePureComponent {
let searchResults;
let hasMore = false;
let loaded;
let noResultsMessage;
let placeholderComponent = PlaceholderStatus;
if (selectedFilter === 'accounts' && results.get('accounts')) {
hasMore = results.get('accountsHasMore');
loaded = results.get('accountsLoaded');
placeholderComponent = PlaceholderAccount;
searchResults = results.get('accounts').size > 0 ? (
<div className={classNames('search-results__section', { 'has-more': hasMore })}>
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
</div>
) : (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.accounts'
defaultMessage='There are no people results for "{term}"'
values={{ term: value }}
/>
</div>
);
if (results.get('accounts').size > 0) {
searchResults = results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />);
} else {
noResultsMessage = (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.accounts'
defaultMessage='There are no people results for "{term}"'
values={{ term: value }}
/>
</div>
);
}
}
if (selectedFilter === 'statuses' && results.get('statuses')) {
hasMore = results.get('statusesHasMore');
loaded = results.get('statusesLoaded');
searchResults = results.get('statuses').size > 0 ? (
<div className={classNames('search-results__section', { 'has-more': hasMore })}>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
</div>
) : (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.statuses'
defaultMessage='There are no posts results for "{term}"'
values={{ term: value }}
/>
</div>
);
if (results.get('statuses').size > 0) {
searchResults = results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />);
} else {
noResultsMessage = (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.statuses'
defaultMessage='There are no posts results for "{term}"'
values={{ term: value }}
/>
</div>
);
}
}
if (selectedFilter === 'hashtags' && results.get('hashtags')) {
hasMore = results.get('hashtagsHasMore');
loaded = results.get('hashtagsLoaded');
placeholderComponent = PlaceholderHashtag;
searchResults = results.get('hashtags').size > 0 ? (
<div className={classNames('search-results__section', { 'has-more': hasMore })}>
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
</div>
) : (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.hashtags'
defaultMessage='There are no hashtags results for "{term}"'
values={{ term: value }}
/>
</div>
);
if (results.get('hashtags').size > 0) {
searchResults = results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />);
} else {
noResultsMessage = (
<div className='empty-column-indicator'>
<FormattedMessage
id='empty_column.search.hashtags'
defaultMessage='There are no hashtags results for "{term}"'
values={{ term: value }}
/>
</div>
);
}
}
return (
<>
{submitted && <FilterBar selectedFilter={submitted ? selectedFilter : null} selectFilter={this.handleSelectFilter} />}
{searchResults}
{hasMore && <LoadMore visible onClick={this.handleLoadMore} />}
{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}
</ScrollableList>
)}
</>
);
}

Wyświetl plik

@ -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>
);
}
}

Wyświetl plik

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

Wyświetl plik

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