Revert "Add pagination support to channel search (#791)"

This reverts commit b3c4ce05fa.

The suggested 2-list approach broke the search bar because the bar is part of the list, so it gets unmounted while the user is typing.  Oops.
This commit is contained in:
infinite-persistence 2022-02-23 21:23:42 +08:00
parent cfd234d615
commit 69de63c436
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
6 changed files with 69 additions and 400 deletions

View file

@ -7,6 +7,7 @@ import {
makeSelectTotalPagesInChannelSearch, makeSelectTotalPagesInChannelSearch,
selectClaimForUri, selectClaimForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { doResolveUris } from 'redux/actions/claims';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
@ -40,6 +41,7 @@ const select = (state, props) => {
}; };
const perform = (dispatch) => ({ const perform = (dispatch) => ({
doResolveUris: (uris, returnCachedUris) => dispatch(doResolveUris(uris, returnCachedUris)),
doFetchChannelLiveStatus: (channelID) => dispatch(doFetchChannelLiveStatus(channelID)), doFetchChannelLiveStatus: (channelID) => dispatch(doFetchChannelLiveStatus(channelID)),
}); });

View file

@ -7,14 +7,13 @@ import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import Button from 'component/button'; import Button from 'component/button';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import ClaimListSearch from 'component/claimListSearch';
import Ads from 'web/component/ads'; import Ads from 'web/component/ads';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import LivestreamLink from 'component/livestreamLink'; import LivestreamLink from 'component/livestreamLink';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search'; import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
import { lighthouse } from 'redux/actions/search';
import ScheduledStreams from 'component/scheduledStreams'; import ScheduledStreams from 'component/scheduledStreams';
import { useIsLargeScreen } from 'effects/use-screensize';
const TYPES_TO_ALLOW_FILTER = ['stream', 'repost']; const TYPES_TO_ALLOW_FILTER = ['stream', 'repost'];
@ -35,6 +34,7 @@ type Props = {
showMature: boolean, showMature: boolean,
tileLayout: boolean, tileLayout: boolean,
viewHiddenChannels: boolean, viewHiddenChannels: boolean,
doResolveUris: (Array<string>, boolean) => void,
claimType: string, claimType: string,
empty?: string, empty?: string,
doFetchChannelLiveStatus: (string) => void, doFetchChannelLiveStatus: (string) => void,
@ -56,6 +56,7 @@ function ChannelContent(props: Props) {
showMature, showMature,
tileLayout, tileLayout,
viewHiddenChannels, viewHiddenChannels,
doResolveUris,
claimType, claimType,
empty, empty,
doFetchChannelLiveStatus, doFetchChannelLiveStatus,
@ -65,8 +66,7 @@ function ChannelContent(props: Props) {
// const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; // const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
const claimsInChannel = 9999; const claimsInChannel = 9999;
const [searchQuery, setSearchQuery] = React.useState(''); const [searchQuery, setSearchQuery] = React.useState('');
const [isSearch, setIsSearch] = React.useState(false); const [searchResults, setSearchResults] = React.useState(undefined);
const isLargeScreen = useIsLargeScreen();
const { const {
location: { pathname, search }, location: { pathname, search },
} = useHistory(); } = useHistory();
@ -78,7 +78,6 @@ function ChannelContent(props: Props) {
(Array.isArray(claimType) (Array.isArray(claimType)
? claimType.every((ct) => TYPES_TO_ALLOW_FILTER.includes(ct)) ? claimType.every((ct) => TYPES_TO_ALLOW_FILTER.includes(ct))
: TYPES_TO_ALLOW_FILTER.includes(claimType)); : TYPES_TO_ALLOW_FILTER.includes(claimType));
const dynamicPageSize = isLargeScreen ? Math.ceil(defaultPageSize * (3 / 2)) : defaultPageSize;
function handleInputChange(e) { function handleInputChange(e) {
const { value } = e.target; const { value } = e.target;
@ -88,17 +87,38 @@ function ChannelContent(props: Props) {
React.useEffect(() => { React.useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (searchQuery.trim().length < 3 || !claimId) { if (searchQuery.trim().length < 3 || !claimId) {
setIsSearch(false); // 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 { } else {
setIsSearch(true); 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); }, DEBOUNCE_WAIT_DURATION_MS);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [claimId, searchQuery]); }, [claimId, searchQuery, showMature, doResolveUris]);
React.useEffect(() => { React.useEffect(() => {
setSearchQuery(''); setSearchQuery('');
setIsSearch(false); setSearchResults(null);
}, [url]); }, [url]);
const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized; const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized;
@ -157,80 +177,44 @@ function ChannelContent(props: Props) {
{/* <Ads type="homepage" /> */} {/* <Ads type="homepage" /> */}
{!fetching && {!fetching && (
(isSearch ? ( <ClaimListDiscover
<ClaimListSearch hasSource
defaultFreshness={CS.FRESH_ALL} defaultFreshness={CS.FRESH_ALL}
showHiddenByUser={viewHiddenChannels} showHiddenByUser={viewHiddenChannels}
fetchViewCount forceShowReposts
hideFilters={!showFilters} fetchViewCount
hideAdvancedFilter={!showFilters} hideFilters={!showFilters}
tileLayout={tileLayout} hideAdvancedFilter={!showFilters}
streamType={SIMPLE_SITE ? CS.CONTENT_ALL : undefined} tileLayout={tileLayout}
channelIds={[claimId]} uris={searchResults}
claimId={claimId} streamType={SIMPLE_SITE ? CS.CONTENT_ALL : undefined}
claimType={claimType} channelIds={[claimId]}
feeAmount={CS.FEE_AMOUNT_ANY} claimType={claimType}
defaultOrderBy={CS.ORDER_BY_NEW} feeAmount={CS.FEE_AMOUNT_ANY}
pageSize={dynamicPageSize} defaultOrderBy={CS.ORDER_BY_NEW}
infiniteScroll={defaultInfiniteScroll} pageSize={defaultPageSize}
injectedItem={SHOW_ADS && !isAuthenticated && IS_WEB && <Ads type="video" />} infiniteScroll={defaultInfiniteScroll}
meta={ injectedItem={SHOW_ADS && !isAuthenticated && IS_WEB && <Ads type="video" />}
showFilters && ( meta={
<Form onSubmit={() => {}} className="wunderbar--inline"> showFilters && (
<Icon icon={ICONS.SEARCH} /> <Form onSubmit={() => {}} className="wunderbar--inline">
<FormField <Icon icon={ICONS.SEARCH} />
className="wunderbar__input--inline" <FormField
value={searchQuery} className="wunderbar__input--inline"
onChange={handleInputChange} value={searchQuery}
type="text" onChange={handleInputChange}
placeholder={__('Search')} type="text"
/> placeholder={__('Search')}
</Form> />
) </Form>
} )
channelIsMine={channelIsMine} }
empty={empty} isChannel
showMature={showMature} channelIsMine={channelIsMine}
searchKeyword={searchQuery} empty={empty}
/> />
) : ( )}
<ClaimListDiscover
hasSource
defaultFreshness={CS.FRESH_ALL}
showHiddenByUser={viewHiddenChannels}
forceShowReposts
fetchViewCount
hideFilters={!showFilters}
hideAdvancedFilter={!showFilters}
tileLayout={tileLayout}
streamType={SIMPLE_SITE ? CS.CONTENT_ALL : undefined}
channelIds={[claimId]}
claimType={claimType}
feeAmount={CS.FEE_AMOUNT_ANY}
defaultOrderBy={CS.ORDER_BY_NEW}
pageSize={defaultPageSize}
infiniteScroll={defaultInfiniteScroll}
injectedItem={SHOW_ADS && !isAuthenticated && IS_WEB && <Ads type="video" />}
meta={
showFilters && (
<Form onSubmit={() => {}} className="wunderbar--inline">
<Icon icon={ICONS.SEARCH} />
<FormField
className="wunderbar__input--inline"
value={searchQuery}
onChange={handleInputChange}
type="text"
placeholder={__('Search')}
/>
</Form>
)
}
isChannel
channelIsMine={channelIsMine}
empty={empty}
/>
))}
</Fragment> </Fragment>
); );
} }

View file

@ -1,47 +0,0 @@
import { connect } from 'react-redux';
import { selectClaimsByUri } from 'redux/selectors/claims';
import {
selectIsSearching,
makeSelectSearchUrisForQuery,
makeSelectHasReachedMaxResultsLength,
} from 'redux/selectors/search';
import { getSearchQueryString } from 'util/query-params';
import { doSearch } from 'redux/actions/search';
import ClaimListSearch from './view';
import { doFetchViewCount } from 'lbryinc';
const select = (state, props) => {
const { searchKeyword, pageSize, claimId, showMature } = props;
const channel_id = encodeURIComponent(claimId);
const isBackgroundSearch = false;
const searchOptions = showMature
? {
channel_id,
isBackgroundSearch,
}
: {
channel_id,
size: pageSize,
nsfw: false,
isBackgroundSearch,
};
const searchQueryString = getSearchQueryString(searchKeyword, searchOptions);
const searchResult = makeSelectSearchUrisForQuery(searchQueryString)(state);
const searchResultLastPageReached = makeSelectHasReachedMaxResultsLength(searchQueryString)(state);
return {
claimsByUri: selectClaimsByUri(state),
loading: props.loading !== undefined ? props.loading : selectIsSearching(state),
searchOptions,
searchResult,
searchResultLastPageReached,
};
};
const perform = {
doFetchViewCount,
doSearch,
};
export default connect(select, perform)(ClaimListSearch);

View file

@ -1,267 +0,0 @@
// @flow
import { SIMPLE_SITE } from 'config';
import type { Node } from 'react';
import * as CS from 'constants/claim_search';
import React from 'react';
import { withRouter } from 'react-router';
import { MATURE_TAGS } from 'constants/tags';
import ClaimList from 'component/claimList';
import ClaimPreview from 'component/claimPreview';
import ClaimPreviewTile from 'component/claimPreviewTile';
import ClaimListHeader from 'component/claimListHeader';
import useFetchViewCount from 'effects/use-fetch-view-count';
export type SearchOptions = {
size?: number,
from?: number,
related_to?: string,
nsfw?: boolean,
channel_id?: string,
isBackgroundSearch?: boolean,
};
type Props = {
uris: Array<string>,
type: string,
pageSize: number,
fetchViewCount?: boolean,
hideAdvancedFilter?: boolean,
hideFilters?: boolean,
infiniteScroll?: Boolean,
showHeader: boolean,
showHiddenByUser?: boolean,
tileLayout: boolean,
defaultOrderBy?: string,
defaultFreshness?: string,
tags: string, // these are just going to be string. pass a CSV if you want multi
defaultTags: string,
claimType?: string | Array<string>,
streamType?: string | Array<string>,
defaultStreamType?: string | Array<string>,
empty?: string,
feeAmount?: string,
repostedClaimId?: string,
maxPages?: number,
channelIds?: Array<string>,
claimId: string,
header?: Node,
headerLabel?: string | Node,
injectedItem: ?Node,
meta?: Node,
location: { search: string, pathname: string },
// --- select ---
claimsByUri: { [string]: any },
loading: boolean,
searchResult: Array<string>,
searchResultLastPageReached: boolean,
// --- perform ---
doFetchViewCount: (claimIdCsv: string) => void,
doSearch: (query: string, options: SearchOptions) => void,
hideLayoutButton?: boolean,
maxClaimRender?: number,
useSkeletonScreen?: boolean,
excludeUris?: Array<string>,
swipeLayout: boolean,
showMature: boolean,
searchKeyword: string,
searchOptions: SearchOptions,
};
function ClaimListSearch(props: Props) {
const {
showHeader = true,
type,
tags,
defaultTags,
loading,
meta,
channelIds,
// eslint-disable-next-line no-unused-vars
claimId,
fetchViewCount,
location,
defaultOrderBy,
headerLabel,
header,
claimType,
pageSize,
streamType,
defaultStreamType = SIMPLE_SITE ? [CS.FILE_VIDEO, CS.FILE_AUDIO] : undefined, // add param for DEFAULT_STREAM_TYPE
defaultFreshness = CS.FRESH_WEEK,
repostedClaimId,
hideAdvancedFilter,
infiniteScroll = true,
injectedItem,
feeAmount,
uris,
tileLayout,
hideFilters = false,
maxPages,
showHiddenByUser = false,
empty,
claimsByUri,
doFetchViewCount,
hideLayoutButton = false,
maxClaimRender,
useSkeletonScreen = true,
excludeUris = [],
swipeLayout = false,
// search
showMature,
searchKeyword,
searchOptions,
searchResult,
searchResultLastPageReached,
doSearch,
} = props;
const { search } = location;
const [page, setPage] = React.useState(1);
const urlParams = new URLSearchParams(search);
const tagsParam = // can be 'x,y,z' or 'x' or ['x','y'] or CS.CONSTANT
(tags && getParamFromTags(tags)) ||
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
(defaultTags && getParamFromTags(defaultTags));
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
const renderUris = uris || searchResult;
// **************************************************************************
// Helpers
// **************************************************************************
function getParamFromTags(t) {
if (t === CS.TAGS_ALL || t === CS.TAGS_FOLLOWED) {
return t;
} else if (Array.isArray(t)) {
return t.join(',');
}
}
function handleScrollBottom() {
if (maxPages !== undefined && page === maxPages) {
return;
}
if (!loading && infiniteScroll) {
if (searchResult && !searchResultLastPageReached) {
setPage(page + 1);
}
}
}
// **************************************************************************
// **************************************************************************
useFetchViewCount(fetchViewCount, renderUris, claimsByUri, doFetchViewCount);
React.useEffect(() => {
doSearch(
searchKeyword,
showMature
? searchOptions
: {
...searchOptions,
from: pageSize * (page - 1),
}
);
}, [doSearch, searchKeyword, showMature, pageSize, page]);
const headerToUse = header || (
<ClaimListHeader
channelIds={channelIds}
defaultTags={defaultTags}
tags={tags}
defaultFreshness={defaultFreshness}
claimType={claimType}
streamType={streamType}
defaultStreamType={defaultStreamType}
feeAmount={feeAmount} // ENABLE_PAID_CONTENT_DISCOVER or something
defaultOrderBy={defaultOrderBy}
hideAdvancedFilter={hideAdvancedFilter}
hasMatureTags={hasMatureTags}
setPage={setPage}
tileLayout={tileLayout}
hideLayoutButton={hideLayoutButton}
hideFilters={hideFilters}
/>
);
return (
<React.Fragment>
{headerLabel && <label className="claim-list__header-label">{headerLabel}</label>}
{tileLayout ? (
<div>
{!repostedClaimId && (
<div className="section__header--actions">
{headerToUse}
{meta && <div className="section__actions--no-margin">{meta}</div>}
</div>
)}
<ClaimList
tileLayout
loading={loading}
uris={renderUris}
onScrollBottom={handleScrollBottom}
page={page}
pageSize={pageSize}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
empty={empty}
maxClaimRender={maxClaimRender}
excludeUris={excludeUris}
swipeLayout={swipeLayout}
/>
{loading && useSkeletonScreen && (
<div className="claim-grid">
{new Array(pageSize).fill(1).map((x, i) => (
<ClaimPreviewTile key={i} placeholder="loading" />
))}
</div>
)}
</div>
) : (
<div>
{showHeader && (
<div className="section__header--actions">
{headerToUse}
{meta && <div className="section__actions--no-margin">{meta}</div>}
</div>
)}
<ClaimList
type={type}
loading={loading}
uris={renderUris}
onScrollBottom={handleScrollBottom}
page={page}
pageSize={pageSize}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
empty={empty}
maxClaimRender={maxClaimRender}
excludeUris={excludeUris}
swipeLayout={swipeLayout}
/>
{loading &&
useSkeletonScreen &&
new Array(pageSize).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" type={type} />)}
</div>
)}
</React.Fragment>
);
}
export default withRouter(ClaimListSearch);

View file

@ -34,7 +34,6 @@ export const SEARCH_OPTIONS = {
TIME_FILTER_THIS_WEEK: 'thisweek', TIME_FILTER_THIS_WEEK: 'thisweek',
TIME_FILTER_THIS_MONTH: 'thismonth', TIME_FILTER_THIS_MONTH: 'thismonth',
TIME_FILTER_THIS_YEAR: 'thisyear', TIME_FILTER_THIS_YEAR: 'thisyear',
CHANNEL_ID: 'channel_id',
}; };
export const SEARCH_PAGE_SIZE = 20; export const SEARCH_PAGE_SIZE = 20;

View file

@ -80,12 +80,10 @@ export const getSearchQueryString = (query: string, options: any = {}) => {
const { related_to } = options; const { related_to } = options;
const { nsfw } = options; const { nsfw } = options;
const { free_only } = options; const { free_only } = options;
const { channel_id } = options;
if (related_to) additionalOptions[SEARCH_OPTIONS.RELATED_TO] = related_to; if (related_to) additionalOptions[SEARCH_OPTIONS.RELATED_TO] = related_to;
if (free_only) additionalOptions[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true; if (free_only) additionalOptions[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true;
if (nsfw === false) additionalOptions[SEARCH_OPTIONS.INCLUDE_MATURE] = false; if (nsfw === false) additionalOptions[SEARCH_OPTIONS.INCLUDE_MATURE] = false;
if (channel_id) additionalOptions[SEARCH_OPTIONS.CHANNEL_ID] = channel_id;
if (additionalOptions) { if (additionalOptions) {
Object.keys(additionalOptions).forEach((key) => { Object.keys(additionalOptions).forEach((key) => {