Livestream category improvements #7115

Merged
infinite-persistence merged 15 commits from ip/category.livestream into master 2021-09-24 16:26:22 +02:00
8 changed files with 12 additions and 202 deletions
Showing only changes of commit ebda47576d - Show all commits

View file

@ -1,12 +1,10 @@
import { connect } from 'react-redux';
import ClaimList from './view';
import { SETTINGS, selectClaimSearchByQuery, selectClaimsByUri } from 'lbry-redux';
import { SETTINGS } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
const select = (state) => ({
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
claimSearchByQuery: selectClaimSearchByQuery(state),
claimsByUri: selectClaimsByUri(state),
});
export default connect(select)(ClaimList);

View file

@ -9,7 +9,6 @@ import { FormField } from 'component/common/form';
import usePersistedState from 'effects/use-persisted-state';
import debounce from 'util/debounce';
import ClaimPreviewTile from 'component/claimPreviewTile';
import { prioritizeActiveLivestreams } from 'component/claimTilesDiscover/view';
const DEBOUNCE_SCROLL_HANDLER_MS = 150;
const SORT_NEW = 'new';
@ -41,9 +40,6 @@ type Props = {
hideMenu?: boolean,
claimSearchByQuery: { [string]: Array<string> },
claimsByUri: { [string]: any },
liveLivestreamsFirst?: boolean,
livestreamMap?: { [string]: any },
searchOptions?: any,
collectionId?: string,
showNoSourceClaims?: boolean,
onClick?: (e: any, claim?: ?Claim, index?: number) => void,
@ -73,11 +69,6 @@ export default function ClaimList(props: Props) {
renderProperties,
searchInLanguage,
hideMenu,
claimSearchByQuery,
claimsByUri,
liveLivestreamsFirst,
livestreamMap,
searchOptions,
collectionId,
showNoSourceClaims,
onClick,
@ -87,23 +78,11 @@ export default function ClaimList(props: Props) {
const timedOut = uris === null;
const urisLength = (uris && uris.length) || 0;
const liveUris = [];
if (liveLivestreamsFirst && livestreamMap) {
prioritizeActiveLivestreams(uris, liveUris, livestreamMap, claimsByUri, claimSearchByQuery, searchOptions);
}
const sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
const noResultMsg = searchInLanguage
? __('No results. Contents may be hidden by the Language filter.')
: __('No results');
const resolveLive = (index) => {
if (liveLivestreamsFirst && livestreamMap && index < liveUris.length) {
return true;
}
return undefined;
};
function handleSortChange() {
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
}
@ -138,13 +117,12 @@ export default function ClaimList(props: Props) {
return tileLayout && !header ? (
<section className="claim-grid">
{urisLength > 0 &&
uris.map((uri, index) => (
uris.map((uri) => (
<ClaimPreviewTile
key={uri}
uri={uri}
showHiddenByUser={showHiddenByUser}
properties={renderProperties}
live={resolveLive(index)}
collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims}
/>
@ -216,7 +194,6 @@ export default function ClaimList(props: Props) {
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
}}
live={resolveLive(index)}
onClick={(e, claim, index) => handleClaimClicked(e, claim, index)}
/>
</React.Fragment>

View file

@ -14,7 +14,6 @@ import ClaimPreviewTile from 'component/claimPreviewTile';
import I18nMessage from 'component/i18nMessage';
import ClaimListHeader from 'component/claimListHeader';
import { useIsLargeScreen } from 'effects/use-screensize';
import { getLivestreamOnlyOptions } from 'util/search';
type Props = {
uris: Array<string>,
@ -31,7 +30,6 @@ type Props = {
includeSupportAction?: boolean,
infiniteScroll?: Boolean,
isChannel?: boolean,
liveLivestreamsFirst?: boolean,
personalView: boolean,
showHeader: boolean,
showHiddenByUser?: boolean,
@ -64,7 +62,6 @@ type Props = {
channelIds?: Array<string>,
claimIds?: Array<string>,
subscribedChannels: Array<Subscription>,
livestreamMap?: { [string]: any },
header?: Node,
headerLabel?: string | Node,
@ -148,8 +145,6 @@ function ClaimListDiscover(props: Props) {
releaseTime,
scrollAnchor,
showHiddenByUser = false,
liveLivestreamsFirst,
livestreamMap,
hasSource,
hasNoSource,
isChannel = false,
@ -400,11 +395,6 @@ function ClaimListDiscover(props: Props) {
// claimSearchResult.splice(2, 0, fixUri);
// }
const livestreamSearchKey = liveLivestreamsFirst
? createNormalizedClaimSearchKey(getLivestreamOnlyOptions(options))
: undefined;
const livestreamSearchResult = livestreamSearchKey && claimSearchByQuery[livestreamSearchKey];
const [finalUris, setFinalUris] = React.useState(
getFinalUrisInitialState(history.action === 'POP', claimSearchResult)
);
@ -571,10 +561,6 @@ function ClaimListDiscover(props: Props) {
if (shouldPerformSearch) {
const searchOptions = JSON.parse(optionsStringForEffect);
doClaimSearch(searchOptions);
if (liveLivestreamsFirst && options.page === 1) {
doClaimSearch(getLivestreamOnlyOptions(searchOptions));
}
}
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]);
@ -586,12 +572,12 @@ function ClaimListDiscover(props: Props) {
}
} else {
// Wait until all queries are done before updating the uris to avoid layout shifts.
const pending = claimSearchResult === undefined || (liveLivestreamsFirst && livestreamSearchResult === undefined);
const pending = claimSearchResult === undefined;
if (!pending && !urisEqual(claimSearchResult, finalUris)) {
setFinalUris(claimSearchResult);
}
}
}, [uris, claimSearchResult, finalUris, setFinalUris, liveLivestreamsFirst, livestreamSearchResult]);
}, [uris, claimSearchResult, finalUris, setFinalUris]);
React.useEffect(() => {
if (fetchViewCount) {
@ -645,8 +631,6 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
liveLivestreamsFirst={liveLivestreamsFirst}
livestreamMap={livestreamMap}
searchOptions={options}
showNoSourceClaims={showNoSourceClaims}
empty={empty}
@ -679,8 +663,6 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
liveLivestreamsFirst={liveLivestreamsFirst}
livestreamMap={livestreamMap}
searchOptions={options}
showNoSourceClaims={hasNoSource || showNoSourceClaims}
empty={empty}

View file

@ -6,90 +6,11 @@ import React from 'react';
import { createNormalizedClaimSearchKey, MATURE_TAGS, splitBySeparator } from 'lbry-redux';
import ClaimPreviewTile from 'component/claimPreviewTile';
import { useHistory } from 'react-router';
import { getLivestreamOnlyOptions } from 'util/search';
/**
* Updates 'uris' by adding and/or moving active livestreams to the front of
* list.
* 'liveUris' is also updated with any entries that were moved to the
* front, for convenience.
*
* @param uris [Ref]
* @param liveUris [Ref]
* @param livestreamMap
* @param claimsByUri
* @param claimSearchByQuery
* @param options
*/
export function prioritizeActiveLivestreams(
uris: Array<string>,
liveUris: Array<string>,
livestreamMap: { [string]: any },
claimsByUri: { [string]: any },
claimSearchByQuery: { [string]: Array<string> },
options: any
) {
if (!livestreamMap || !uris) return;
const claimIsLive = (claim, liveChannelIds) => {
// This function relies on:
// 1. Only 1 actual livestream per channel (i.e. all other livestream-claims
// for that channel actually point to the same source).
// 2. 'liveChannelIds' needs to be pruned after being accounted for,
// otherwise all livestream-claims will be "live" (we'll only take the
// latest one as "live" ).
return (
claim &&
claim.value_type === 'stream' &&
claim.value.source === undefined &&
claim.signing_channel &&
liveChannelIds.includes(claim.signing_channel.claim_id)
);
};
let liveChannelIds = Object.keys(livestreamMap);
// 1. Collect active livestreams from the primary search to put in front.
uris.forEach((uri) => {
const claim = claimsByUri[uri];
if (claimIsLive(claim, liveChannelIds)) {
liveUris.push(uri);
// This live channel has been accounted for, so remove it.
liveChannelIds.splice(liveChannelIds.indexOf(claim.signing_channel.claim_id), 1);
}
});
// 2. Now, repeat on the secondary search.
if (options) {
const livestreamsOnlySearchCacheQuery = createNormalizedClaimSearchKey(getLivestreamOnlyOptions(options));
const livestreamsOnlyUris = claimSearchByQuery[livestreamsOnlySearchCacheQuery];
if (livestreamsOnlyUris) {
livestreamsOnlyUris.forEach((uri) => {
const claim = claimsByUri[uri];
if (!uris.includes(uri) && claimIsLive(claim, liveChannelIds)) {
liveUris.push(uri);
// This live channel has been accounted for, so remove it.
liveChannelIds.splice(liveChannelIds.indexOf(claim.signing_channel.claim_id), 1);
}
});
}
}
// 3. Finalize uris by putting live livestreams in front.
const newUris = liveUris.concat(uris.filter((uri) => !liveUris.includes(uri)));
uris.splice(0, uris.length, ...newUris);
}
// ****************************************************************************
// ClaimTilesDiscover
// ****************************************************************************
type Props = {
prefixUris?: Array<string>,
pinUrls?: Array<string>,
uris: Array<string>,
liveLivestreamsFirst?: boolean,
livestreamMap?: { [string]: any },
showNoSourceClaims?: boolean,
renderProperties?: (Claim) => ?Node,
fetchViewCount?: boolean,
@ -148,8 +69,6 @@ function ClaimTilesDiscover(props: Props) {
renderProperties,
blockedUris,
mutedUris,
liveLivestreamsFirst,
livestreamMap,
pinUrls,
prefixUris,
showNoSourceClaims,
@ -163,7 +82,7 @@ function ClaimTilesDiscover(props: Props) {
const mutedAndBlockedChannelIds = Array.from(
new Set(mutedUris.concat(blockedUris).map((uri) => splitBySeparator(uri)[1]))
);
const liveUris = [];
let streamTypesParam;
if (streamTypes) {
streamTypesParam = streamTypes;
@ -245,28 +164,16 @@ function ClaimTilesDiscover(props: Props) {
options.claim_ids = claimIds;
}
const mainSearchKey = createNormalizedClaimSearchKey(options);
const livestreamSearchKey = liveLivestreamsFirst
? createNormalizedClaimSearchKey(getLivestreamOnlyOptions(options))
: undefined;
const searchKey = createNormalizedClaimSearchKey(options);
const isLoading = fetchingClaimSearchByQuery[searchKey];
let uris = (prefixUris || []).concat(claimSearchByQuery[mainSearchKey] || []);
const isLoading = fetchingClaimSearchByQuery[mainSearchKey];
if (liveLivestreamsFirst && livestreamMap && !isLoading) {
prioritizeActiveLivestreams(uris, liveUris, livestreamMap, claimsByUri, claimSearchByQuery, options);
}
let uris = (prefixUris || []).concat(claimSearchByQuery[searchKey] || []);
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
const optionsStringForEffect = JSON.stringify(options);
const shouldPerformSearch = !isLoading && uris.length === 0;
if (
prefixUris === undefined &&
(claimSearchByQuery[mainSearchKey] === undefined ||
(livestreamSearchKey && claimSearchByQuery[livestreamSearchKey] === undefined))
) {
if (prefixUris === undefined && claimSearchByQuery[searchKey] === undefined) {
// This is a new query and we don't have results yet ...
if (prevUris.length !== 0) {
// ... but we have previous results. Use it until new results are here.
@ -288,16 +195,6 @@ function ClaimTilesDiscover(props: Props) {
modifiedUris.splice(2, 0, ...fixUris);
}
// **************************************************************************
// **************************************************************************
function resolveLive(index) {
if (liveLivestreamsFirst && livestreamMap && index < liveUris.length) {
return true;
}
return undefined;
}
function fetchViewCountForUris(uris) {
const claimIds = [];
@ -314,19 +211,12 @@ function ClaimTilesDiscover(props: Props) {
}
}
// **************************************************************************
// **************************************************************************
React.useEffect(() => {
if (shouldPerformSearch) {
const searchOptions = JSON.parse(optionsStringForEffect);
doClaimSearch(searchOptions);
if (liveLivestreamsFirst) {
doClaimSearch(getLivestreamOnlyOptions(searchOptions));
}
}
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, liveLivestreamsFirst]);
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]);
React.useEffect(() => {
if (JSON.stringify(prevUris) !== JSON.stringify(uris) && !shouldPerformSearch) {
@ -345,13 +235,12 @@ function ClaimTilesDiscover(props: Props) {
return (
<ul className="claim-grid">
{modifiedUris && modifiedUris.length
? modifiedUris.map((uri, index) => (
? modifiedUris.map((uri) => (
<ClaimPreviewTile
showNoSourceClaims={hasNoSource || showNoSourceClaims}
key={uri}
uri={uri}
properties={renderProperties}
live={resolveLive(index)}
/>
))
: new Array(pageSize)

View file

@ -9,7 +9,6 @@ import ClaimListDiscover from 'component/claimListDiscover';
import Page from 'component/page';
import Button from 'component/button';
import Icon from 'component/common/icon';
import useGetLivestreams from 'effects/use-get-livestreams';
import { splitBySeparator } from 'lbry-redux';
type Props = {
@ -20,7 +19,6 @@ type Props = {
function ChannelsFollowingPage(props: Props) {
const { subscribedChannels, tileLayout } = props;
const hasSubsribedChannels = subscribedChannels.length > 0;
const { livestreamMap } = useGetLivestreams();
return !hasSubsribedChannels ? (
<ChannelsFollowingDiscoverPage />
@ -46,8 +44,6 @@ function ChannelsFollowingPage(props: Props) {
navigate={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`}
/>
}
liveLivestreamsFirst
livestreamMap={livestreamMap}
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
hasSource
/>

View file

@ -15,7 +15,6 @@ import Icon from 'component/common/icon';
import Ads from 'web/component/ads';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import useGetLivestreams from 'effects/use-get-livestreams';
import moment from 'moment';
type Props = {
@ -45,7 +44,6 @@ function DiscoverPage(props: Props) {
const buttonRef = useRef();
const isHovering = useHover(buttonRef);
const isMobile = useIsMobile();
const { livestreamMap } = useGetLivestreams();
const urlParams = new URLSearchParams(search);
const claimType = urlParams.get('claim_type');
@ -172,8 +170,6 @@ function DiscoverPage(props: Props) {
)
)
}
liveLivestreamsFirst
livestreamMap={livestreamMap}
hasSource
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
/>

View file

@ -9,7 +9,6 @@ import ClaimTilesDiscover from 'component/claimTilesDiscover';
import ClaimPreviewTile from 'component/claimPreviewTile';
import Icon from 'component/common/icon';
import WaitUntilOnPage from 'component/common/wait-until-on-page';
import useGetLivestreams from 'effects/use-get-livestreams';
import { GetLinksData } from 'util/buildHomepage';
// @if TARGET='web'
@ -30,7 +29,6 @@ function HomePage(props: Props) {
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0;
const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
const { livestreamMap } = useGetLivestreams();
const rowData: Array<RowDataItem> = GetLinksData(
homepageData,
@ -53,14 +51,7 @@ function HomePage(props: Props) {
</ul>
);
const claimTiles = (
<ClaimTilesDiscover
{...options}
liveLivestreamsFirst
livestreamMap={livestreamMap}
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
hasSource
pinUrls={pinUrls}
/>
<ClaimTilesDiscover {...options} showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS} hasSource pinUrls={pinUrls} />
);
return (

View file

@ -21,25 +21,6 @@ export function createNormalizedSearchKey(query: string) {
return normalizedQuery;
}
/**
* Returns the "livestream only" version of the given 'options'.
*
* Currently, the 'has_source' attribute is being used to identify livestreams.
*
* @param options
* @returns {*}
*/
export function getLivestreamOnlyOptions(options: any) {
const newOptions = Object.assign({}, options);
delete newOptions.has_source;
delete newOptions.stream_types;
newOptions.has_no_source = true;
newOptions.claim_type = ['stream'];
newOptions.page_size = 50;
newOptions.order_by = ['release_time'];
return newOptions;
}
/**
* getUriForSearchTerm
* @param term