// @flow import type { Node } from 'react'; import * as PAGES from 'constants/pages'; import React, { Fragment, useEffect, useState } from 'react'; import { withRouter } from 'react-router'; import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux'; import { FormField } from 'component/common/form'; import Button from 'component/button'; import moment from 'moment'; import ClaimList from 'component/claimList'; import Tag from 'component/tag'; import ClaimPreview from 'component/claimPreview'; import { toCapitalCase } from 'util/string'; import I18nMessage from 'component/i18nMessage'; const PAGE_SIZE = 20; const TIME_DAY = 'day'; const TIME_WEEK = 'week'; const TIME_MONTH = 'month'; const TIME_YEAR = 'year'; const TIME_ALL = 'all'; const SEARCH_SORT_YOU = 'you'; const SEARCH_SORT_ALL = 'everyone'; const SEARCH_SORT_CHANNELS = 'channels'; const TYPE_TRENDING = 'trending'; const TYPE_TOP = 'top'; const TYPE_NEW = 'new'; const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, SEARCH_SORT_ALL]; const SEARCH_TYPES = [TYPE_TRENDING, TYPE_TOP, TYPE_NEW]; const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL]; type Props = { uris: Array, subscribedChannels: Array, doClaimSearch: ({}) => void, tags: Array, loading: boolean, personalView: boolean, doToggleTagFollow: string => void, meta?: Node, showNsfw: boolean, hideCustomization: boolean, history: { action: string, push: string => void, replace: string => void }, location: { search: string, pathname: string }, claimSearchByQuery: { [string]: Array, }, hiddenUris: Array, hiddenNsfwMessage?: Node, }; function ClaimListDiscover(props: Props) { const { doClaimSearch, claimSearchByQuery, tags, loading, personalView, meta, subscribedChannels, showNsfw, history, location, hiddenUris, hideCustomization, hiddenNsfwMessage, } = props; const didNavigateForward = history.action === 'PUSH'; const [page, setPage] = useState(1); const { search } = location; const [forceRefresh, setForceRefresh] = useState(); const urlParams = new URLSearchParams(search); const personalSort = urlParams.get('sort') || (hideCustomization ? SEARCH_SORT_ALL : SEARCH_SORT_YOU); const typeSort = urlParams.get('type') || TYPE_TRENDING; const timeSort = urlParams.get('time') || TIME_WEEK; const tagsInUrl = urlParams.get('t') || ''; const options: { page_size: number, page: number, no_totals: boolean, any_tags: Array, channel_ids: Array, not_channel_ids: Array, not_tags: Array, order_by: Array, release_time?: string, } = { page_size: PAGE_SIZE, page, // no_totals makes it so the sdk doesn't have to calculate total number pages for pagination // it's faster, but we will need to remove it if we start using total_pages no_totals: true, any_tags: (personalView && !hideCustomization && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [], channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [], not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [], not_tags: !showNsfw ? MATURE_TAGS : [], order_by: typeSort === TYPE_TRENDING ? ['trending_group', 'trending_mixed'] : typeSort === TYPE_NEW ? ['release_time'] : ['effective_amount'], // Sort by top }; if (typeSort === TYPE_TOP && timeSort !== TIME_ALL) { options.release_time = `>${Math.floor( moment() .subtract(1, timeSort) .unix() )}`; } const hasContent = (personalSort === SEARCH_SORT_CHANNELS && subscribedChannels.length) || (personalSort === SEARCH_SORT_YOU && !!tags.length) || personalSort === SEARCH_SORT_ALL; const hasMatureTags = tags.some(t => MATURE_TAGS.includes(t)); const claimSearchCacheQuery = createNormalizedClaimSearchKey(options); const uris = (hasContent && claimSearchByQuery[claimSearchCacheQuery]) || []; const shouldPerformSearch = hasContent && (uris.length === 0 || didNavigateForward || (!loading && uris.length < PAGE_SIZE * page && uris.length % PAGE_SIZE === 0)); // Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time const optionsStringForEffect = JSON.stringify(options); const noChannels = (

You're not following any channels.

), discover:

); const noResults = (

setForceRefresh(Date.now())} /> ), }} > Sorry, your request timed out. %again%

, }} > If you continue to have issues, please %support%.

); const noTags = (

, }} > You're not following any tags. Add tags above or smash that %customize% button!

); const noFollowing = (personalSort === SEARCH_SORT_YOU && noTags) || (personalSort === SEARCH_SORT_CHANNELS && noChannels); const emptyState = !loading && !hasContent ? noFollowing : noResults; function getSearch() { let search = `?`; if (!personalView) { search += `t=${tagsInUrl}&`; } return search; } function handleTypeSort(newTypeSort) { let url = `${getSearch()}type=${newTypeSort}&sort=${personalSort}`; if (newTypeSort === TYPE_TOP) { url += `&time=${timeSort}`; } setPage(1); history.push(url); } function handlePersonalSort(newPersonalSort) { setPage(1); history.push(`${getSearch()}type=${typeSort}&sort=${newPersonalSort}`); } function handleTimeSort(newTimeSort) { setPage(1); history.push(`${getSearch()}type=${typeSort}&sort=${personalSort}&time=${newTimeSort}`); } function handleScrollBottom() { if (!loading) { setPage(page + 1); } } useEffect(() => { if (shouldPerformSearch) { const searchOptions = JSON.parse(optionsStringForEffect); doClaimSearch(searchOptions); } }, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]); const header = ( handleTypeSort(e.target.value)} > {SEARCH_TYPES.map(type => ( ))} {!hideCustomization && ( {__('for')} {!personalView && tags && tags.length ? ( tags.map(tag => ) ) : ( { handlePersonalSort(e.target.value); }} > {SEARCH_FILTER_TYPES.map(type => ( ))} )} )} {typeSort === 'top' && ( handleTimeSort(e.target.value)} > {SEARCH_TIMES.map(time => ( ))} )} {hasMatureTags && hiddenNsfwMessage} ); return (
{loading && new Array(PAGE_SIZE).fill(1).map((x, i) => )}
); } export default withRouter(ClaimListDiscover);