Channel Search pagination
Closes 605 "Add pagination support to channel search"
## Previous Attempt
The previous attempt (69de63c4
) didn't work because the wunderbar is part of the list component, so it is unmounted when we switch between the Normal and Filtered list, causing it to lose focus while typing.
Also, creating another full-blown ClaimList* component is really redundant (we should be consolidating instead).
## Approach
ClaimListDiscover recently added a new `subSection`, so we can place the filtered `ClaimList` here without causing the wunderbar to unmount.
Wrapped the "lighthouse search with channel_id" into `searchResults.jsx` for now as a quick and isolated solution. When we refactor ClaimList*, we can then consider incorporating into `doSearch`.
This commit is contained in:
parent
7904e751ac
commit
efbbba6958
2 changed files with 106 additions and 38 deletions
92
ui/component/channelContent/internal/searchResults.jsx
Normal file
92
ui/component/channelContent/internal/searchResults.jsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import ClaimList from 'component/claimList';
|
||||
import { DEBOUNCE_WAIT_DURATION_MS, SEARCH_PAGE_SIZE } from 'constants/search';
|
||||
import { lighthouse } from 'redux/actions/search';
|
||||
|
||||
type Props = {
|
||||
searchQuery: string,
|
||||
claimId: ?string,
|
||||
showMature: ?boolean,
|
||||
tileLayout: boolean,
|
||||
onResults?: (results: ?Array<string>) => void,
|
||||
doResolveUris: (Array<string>, boolean) => void,
|
||||
};
|
||||
|
||||
export function SearchResults(props: Props) {
|
||||
const { searchQuery, claimId, showMature, tileLayout, onResults, doResolveUris } = props;
|
||||
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [searchResults, setSearchResults] = React.useState(undefined);
|
||||
const [isSearching, setIsSearching] = React.useState(false);
|
||||
const noMoreResults = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setPage(1);
|
||||
}, [searchQuery]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onResults) {
|
||||
onResults(searchResults);
|
||||
}
|
||||
}, [searchResults, onResults]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (searchQuery.trim().length < 3 || !claimId) {
|
||||
return setSearchResults(null);
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
|
||||
lighthouse
|
||||
.search(
|
||||
`from=${SEARCH_PAGE_SIZE * (page - 1)}` +
|
||||
`&s=${encodeURIComponent(searchQuery)}` +
|
||||
`&channel_id=${encodeURIComponent(claimId)}` +
|
||||
`&nsfw=${showMature ? 'true' : 'false'}` +
|
||||
`&size=${SEARCH_PAGE_SIZE}`
|
||||
)
|
||||
.then(({ body: results }) => {
|
||||
const urls = results.map(({ name, claimId }) => {
|
||||
return `lbry://${name}#${claimId}`;
|
||||
});
|
||||
|
||||
// Batch-resolve the urls before calling 'setSearchResults', as the
|
||||
// latter will immediately cause the tiles to resolve, ending up
|
||||
// calling doResolveUri one by one before the batched one.
|
||||
doResolveUris(urls, true);
|
||||
|
||||
// De-dup (LH will return some duplicates) and concat results
|
||||
setSearchResults((prev) => (page === 1 ? urls : Array.from(new Set((prev || []).concat(urls)))));
|
||||
noMoreResults.current = !urls || urls.length < SEARCH_PAGE_SIZE;
|
||||
})
|
||||
.catch(() => {
|
||||
setPage(1);
|
||||
setSearchResults(null);
|
||||
noMoreResults.current = false;
|
||||
})
|
||||
.finally(() => {
|
||||
setIsSearching(false);
|
||||
});
|
||||
}, DEBOUNCE_WAIT_DURATION_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, claimId, page, showMature, doResolveUris]);
|
||||
|
||||
if (!searchResults) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClaimList
|
||||
uris={searchResults}
|
||||
loading={isSearching}
|
||||
onScrollBottom={() => setPage((prev) => (noMoreResults.current ? prev : prev + 1))}
|
||||
page={page}
|
||||
pageSize={SEARCH_PAGE_SIZE}
|
||||
tileLayout={tileLayout}
|
||||
useLoadingSpinner
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -11,9 +11,8 @@ import Ads from 'web/component/ads';
|
|||
import Icon from 'component/common/icon';
|
||||
import LivestreamLink from 'component/livestreamLink';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
|
||||
import { lighthouse } from 'redux/actions/search';
|
||||
import ScheduledStreams from 'component/scheduledStreams';
|
||||
import { SearchResults } from './internal/searchResults';
|
||||
|
||||
const TYPES_TO_ALLOW_FILTER = ['stream', 'repost'];
|
||||
|
||||
|
@ -66,7 +65,7 @@ function ChannelContent(props: Props) {
|
|||
// const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||
const claimsInChannel = 9999;
|
||||
const [searchQuery, setSearchQuery] = React.useState('');
|
||||
const [searchResults, setSearchResults] = React.useState(undefined);
|
||||
const [isSearching, setIsSearching] = React.useState(false);
|
||||
const {
|
||||
location: { pathname, search },
|
||||
} = useHistory();
|
||||
|
@ -84,41 +83,8 @@ function ChannelContent(props: Props) {
|
|||
setSearchQuery(value);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (searchQuery.trim().length < 3 || !claimId) {
|
||||
// In order to display original search results, search results must be set to null. A query of '' should display original results.
|
||||
return setSearchResults(null);
|
||||
} else {
|
||||
lighthouse
|
||||
.search(
|
||||
`s=${encodeURIComponent(searchQuery)}&channel_id=${encodeURIComponent(claimId)}${
|
||||
!showMature ? '&nsfw=false&size=50&from=0' : ''
|
||||
}`
|
||||
)
|
||||
.then(({ body: results }) => {
|
||||
const urls = results.map(({ name, claimId }) => {
|
||||
return `lbry://${name}#${claimId}`;
|
||||
});
|
||||
|
||||
// Batch-resolve the urls before calling 'setSearchResults', as the
|
||||
// latter will immediately cause the tiles to resolve, ending up
|
||||
// calling doResolveUri one by one before the batched one.
|
||||
doResolveUris(urls, true);
|
||||
|
||||
setSearchResults(urls);
|
||||
})
|
||||
.catch(() => {
|
||||
setSearchResults(null);
|
||||
});
|
||||
}
|
||||
}, DEBOUNCE_WAIT_DURATION_MS);
|
||||
return () => clearTimeout(timer);
|
||||
}, [claimId, searchQuery, showMature, doResolveUris]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSearchQuery('');
|
||||
setSearchResults(null);
|
||||
}, [url]);
|
||||
|
||||
const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized;
|
||||
|
@ -186,7 +152,7 @@ function ChannelContent(props: Props) {
|
|||
hideFilters={!showFilters}
|
||||
hideAdvancedFilter={!showFilters}
|
||||
tileLayout={tileLayout}
|
||||
uris={searchResults}
|
||||
uris={isSearching ? [] : null}
|
||||
streamType={SIMPLE_SITE ? CS.CONTENT_ALL : undefined}
|
||||
channelIds={[claimId]}
|
||||
claimType={claimType}
|
||||
|
@ -210,9 +176,19 @@ function ChannelContent(props: Props) {
|
|||
</Form>
|
||||
)
|
||||
}
|
||||
subSection={
|
||||
<SearchResults
|
||||
searchQuery={searchQuery}
|
||||
claimId={claimId}
|
||||
showMature={showMature}
|
||||
tileLayout={tileLayout}
|
||||
onResults={(results) => setIsSearching(results !== null)}
|
||||
doResolveUris={doResolveUris}
|
||||
/>
|
||||
}
|
||||
isChannel
|
||||
channelIsMine={channelIsMine}
|
||||
empty={empty}
|
||||
empty={isSearching ? ' ' : empty}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
|
Loading…
Reference in a new issue