Skip to content

Commit

Permalink
Livestream category improvements (#7115)
Browse files Browse the repository at this point in the history
* ❌ Remove old method of displaying active livestreams

Completely remove it for now to make the commit deltas clearer.
We'll replace it with the new method at the end.

* Fetch and store active-livestream info in redux

* Tiles can now query active-livestream state from redux instead of getting from parent.

* ⏪ ClaimTilesDiscover: revert and cleanup

## Simplify
- Simplify to just `uris` instead of having multiple arrays (`uris`, `modifiedUris`, `prevUris`)
- The `prevUris` is for CLS prevention. With this removal, the CLS issue is back, but we'll handle it differently later.
- Temporarily disable the view-count fetching. Code is left there so that I don't forget.

## Fix
- `shouldPerformSearch` was never true when `prefixUris` is present. Corrected the logic.
- Aside: prefix and pin is so similar in function. Hm ....

* ClaimTilesDiscover: factor out options

## Change
Move the `option` code outside and passed in as a pre-calculated prop.

## Reason
To skip rendering while waiting for `claim_search`, we need to add `React.memo(areEqual)`. However, the flag that determines if we are fetching `claim_search` (fetchingClaimSearchByQuery[]) depends on the derived options as the key.

Instead of calculating `options` twice, we moved it to the props so both sides can use it.

It also makes the component a bit more readable.

The downside is that the prop-passing might not be clear.

* ClaimTilesDiscover: reduce ~17 renders at startup to just 2.

* ClaimTilesDiscover: fill with placeholder while waiting for claim_search

## Issue
Livestream claims are fetched seperately, so they might already exists. While claim_search is running, the list only consists of livestreams (collapsed).

## Fix
Fill up the space with placeholders to prevent layout shift.

* Add 'useFetchViewCount' to handle fetching from lists

This effect also stashes fetched uris, so that we won't re-fetch the same uris during the same instance (e.g. during infinite scroll).

* ⏪ ClaimListDiscover: revert and cleanup

## Revert
- Removed the 'finalUris' stuff that was meant to "pause" visual changes when fetching. I think it'll be cleaner to use React.memo to achieve that.

## Alterations
- Added `renderUri` to make it clear which array that this component will render.
- Re-do the way we fetch view counts now that 'finalUris' is gone. Not the best method, but at least correct for now.

* ClaimListDiscover: add prefixUris, similar to ClaimTilesDiscover

This will be initially used to append livestreams at the top.

* ✅ Re-enable active livestream tiles using the new method

* doFetchActiveLivestreams: add interval check

- Added a default minimum of 5 minutes between fetches. Clients can bypass this through `forceFetch` if needed.

* doFetchActiveLivestreams: add option check

We'll need to support different 'orderBy', so adding an "options check" when determining if we just made the same fetch.

* WildWest: limit livestream tiles + add ability to show more

Most likely this behavior will change in the future, so we'll leave `ClaimListDiscover` untouched and handle the logic at the page level.

This solution uses 2 `ClaimListDiscover` -- if the reduced livestream list is visible, it handles the header; else the normal list handles the header.

* Use better tile-count on larger screens.

Used the same method as how the homepage does it.
  • Loading branch information
infinite-persistence committed Sep 24, 2021
1 parent b78899d commit 3b47edc
Show file tree
Hide file tree
Showing 25 changed files with 632 additions and 529 deletions.
14 changes: 14 additions & 0 deletions flow-typed/livestream.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,18 @@ declare type LivestreamReplayData = Array<LivestreamReplayItem>;
declare type LivestreamState = {
fetchingById: {},
viewersById: {},
fetchingActiveLivestreams: boolean,
activeLivestreams: ?LivestreamInfo,
activeLivestreamsLastFetchedDate: number,
activeLivestreamsLastFetchedOptions: {},
}

declare type LivestreamInfo = {
[/* creatorId */ string]: {
live: boolean,
viewCount: number,
creatorId: string,
latestClaimId: string,
latestClaimUri: string,
}
}
4 changes: 1 addition & 3 deletions ui/component/claimList/index.js
Original file line number Diff line number Diff line change
@@ -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);
32 changes: 8 additions & 24 deletions ui/component/claimList/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ 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';
const SORT_OLD = 'old';

type Props = {
uris: Array<string>,
prefixUris?: Array<string>,
header: Node | boolean,
headerAltControls: Node,
loading: boolean,
Expand All @@ -41,9 +41,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,
Expand All @@ -53,6 +50,7 @@ export default function ClaimList(props: Props) {
const {
activeUri,
uris,
prefixUris,
headerAltControls,
loading,
persistedStorageKey,
Expand All @@ -73,37 +71,25 @@ export default function ClaimList(props: Props) {
renderProperties,
searchInLanguage,
hideMenu,
claimSearchByQuery,
claimsByUri,
liveLivestreamsFirst,
livestreamMap,
searchOptions,
collectionId,
showNoSourceClaims,
onClick,
} = props;

const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);

// Exclude prefix uris in these results variables. We don't want to show
// anything if the search failed or timed out.
const timedOut = uris === null;
const urisLength = (uris && uris.length) || 0;

const liveUris = [];
if (liveLivestreamsFirst && livestreamMap) {
prioritizeActiveLivestreams(uris, liveUris, livestreamMap, claimsByUri, claimSearchByQuery, searchOptions);
}
const tileUris = (prefixUris || []).concat(uris);
const sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? tileUris : tileUris.slice().reverse())) || [];

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);
}
Expand Down Expand Up @@ -138,13 +124,12 @@ export default function ClaimList(props: Props) {
return tileLayout && !header ? (
<section className="claim-grid">
{urisLength > 0 &&
uris.map((uri, index) => (
tileUris.map((uri) => (
<ClaimPreviewTile
key={uri}
uri={uri}
showHiddenByUser={showHiddenByUser}
properties={renderProperties}
live={resolveLive(index)}
collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims}
/>
Expand Down Expand Up @@ -216,7 +201,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>
Expand Down
96 changes: 14 additions & 82 deletions ui/component/claimListDiscover/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import ClaimPreview from 'component/claimPreview';
import ClaimPreviewTile from 'component/claimPreviewTile';
import I18nMessage from 'component/i18nMessage';
import ClaimListHeader from 'component/claimListHeader';
import useFetchViewCount from 'effects/use-fetch-view-count';
import { useIsLargeScreen } from 'effects/use-screensize';
import { getLivestreamOnlyOptions } from 'util/search';

type Props = {
uris: Array<string>,
prefixUris?: Array<string>,
name?: string,
type: string,
pageSize?: number,
Expand All @@ -31,7 +32,6 @@ type Props = {
includeSupportAction?: boolean,
infiniteScroll?: Boolean,
isChannel?: boolean,
liveLivestreamsFirst?: boolean,
personalView: boolean,
showHeader: boolean,
showHiddenByUser?: boolean,
Expand Down Expand Up @@ -64,7 +64,6 @@ type Props = {
channelIds?: Array<string>,
claimIds?: Array<string>,
subscribedChannels: Array<Subscription>,
livestreamMap?: { [string]: any },

header?: Node,
headerLabel?: string | Node,
Expand Down Expand Up @@ -137,6 +136,7 @@ function ClaimListDiscover(props: Props) {
injectedItem,
feeAmount,
uris,
prefixUris,
tileLayout,
hideFilters = false,
claimIds,
Expand All @@ -148,8 +148,6 @@ function ClaimListDiscover(props: Props) {
releaseTime,
scrollAnchor,
showHiddenByUser = false,
liveLivestreamsFirst,
livestreamMap,
hasSource,
hasNoSource,
isChannel = false,
Expand Down Expand Up @@ -380,9 +378,9 @@ function ClaimListDiscover(props: Props) {

const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));

const mainSearchKey = createNormalizedClaimSearchKey(options);
let claimSearchResult = claimSearchByQuery[mainSearchKey];
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[mainSearchKey];
const searchKey = createNormalizedClaimSearchKey(options);
const claimSearchResult = claimSearchByQuery[searchKey];
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[searchKey];

// uncomment to fix an item on a page
// const fixUri = 'lbry://@corbettreport#0/lbryodysee#5';
Expand All @@ -400,14 +398,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)
);
const [prevOptions, setPrevOptions] = React.useState(null);

if (!isJustScrollingToNewPage(prevOptions, options)) {
Expand Down Expand Up @@ -469,6 +459,8 @@ function ClaimListDiscover(props: Props) {
</div>
);

const renderUris = uris || claimSearchResult;

// **************************************************************************
// Helpers
// **************************************************************************
Expand Down Expand Up @@ -514,41 +506,6 @@ function ClaimListDiscover(props: Props) {
}
}

function urisEqual(prev: Array<string>, next: Array<string>) {
if (!prev || !next) {
// From 'ClaimList', "null" and "undefined" have special meaning,
// so we can't just compare array length here.
// - null = "timed out"
// - undefined = "no result".
return prev === next;
}
return prev.length === next.length && prev.every((value, index) => value === next[index]);
}

function getFinalUrisInitialState(isNavigatingBack, claimSearchResult) {
if (isNavigatingBack && claimSearchResult && claimSearchResult.length > 0) {
return claimSearchResult;
} else {
return [];
}
}

function fetchViewCountForUris(uris) {
const claimIds = [];

if (uris) {
uris.forEach((uri) => {
if (claimsByUri[uri]) {
claimIds.push(claimsByUri[uri].claim_id);
}
});
}

if (claimIds.length > 0) {
doFetchViewCount(claimIds.join(','));
}
}

function resolveOrderByOption(orderBy: string | Array<string>, sortBy: string | Array<string>) {
const order_by =
orderBy === CS.ORDER_BY_TRENDING
Expand All @@ -567,38 +524,15 @@ function ClaimListDiscover(props: Props) {
// **************************************************************************
// **************************************************************************

useFetchViewCount(fetchViewCount, renderUris, claimsByUri, doFetchViewCount);

React.useEffect(() => {
if (shouldPerformSearch) {
const searchOptions = JSON.parse(optionsStringForEffect);
doClaimSearch(searchOptions);

if (liveLivestreamsFirst && options.page === 1) {
doClaimSearch(getLivestreamOnlyOptions(searchOptions));
}
}
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]);

// Resolve 'finalUri'
React.useEffect(() => {
if (uris) {
if (!urisEqual(uris, finalUris)) {
setFinalUris(uris);
}
} else {
// Wait until all queries are done before updating the uris to avoid layout shifts.
const pending = claimSearchResult === undefined || (liveLivestreamsFirst && livestreamSearchResult === undefined);
if (!pending && !urisEqual(claimSearchResult, finalUris)) {
setFinalUris(claimSearchResult);
}
}
}, [uris, claimSearchResult, finalUris, setFinalUris, liveLivestreamsFirst, livestreamSearchResult]);

React.useEffect(() => {
if (fetchViewCount) {
fetchViewCountForUris(finalUris);
}
}, [finalUris]); // eslint-disable-line react-hooks/exhaustive-deps

const headerToUse = header || (
<ClaimListHeader
channelIds={channelIds}
Expand Down Expand Up @@ -636,7 +570,8 @@ function ClaimListDiscover(props: Props) {
<ClaimList
tileLayout
loading={loading}
uris={finalUris}
uris={renderUris}
prefixUris={prefixUris}
onScrollBottom={handleScrollBottom}
page={page}
pageSize={dynamicPageSize}
Expand All @@ -645,8 +580,6 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
liveLivestreamsFirst={liveLivestreamsFirst}
livestreamMap={livestreamMap}
searchOptions={options}
showNoSourceClaims={showNoSourceClaims}
empty={empty}
Expand All @@ -670,7 +603,8 @@ function ClaimListDiscover(props: Props) {
<ClaimList
type={type}
loading={loading}
uris={finalUris}
uris={renderUris}
prefixUris={prefixUris}
onScrollBottom={handleScrollBottom}
page={page}
pageSize={dynamicPageSize}
Expand All @@ -679,8 +613,6 @@ function ClaimListDiscover(props: Props) {
includeSupportAction={includeSupportAction}
injectedItem={injectedItem}
showHiddenByUser={showHiddenByUser}
liveLivestreamsFirst={liveLivestreamsFirst}
livestreamMap={livestreamMap}
searchOptions={options}
showNoSourceClaims={hasNoSource || showNoSourceClaims}
empty={empty}
Expand Down
2 changes: 2 additions & 0 deletions ui/component/claimPreview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from 'lbry-redux';
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
import { selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
Expand Down Expand Up @@ -56,6 +57,7 @@ const select = (state, props) => {
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
isLivestreamActive: makeSelectIsActiveLivestream(props.uri)(state),
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
Expand Down
10 changes: 5 additions & 5 deletions ui/component/claimPreview/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type Props = {
repostUrl?: string,
hideMenu?: boolean,
isLivestream?: boolean,
live?: boolean,
isLivestreamActive: boolean,
collectionId?: string,
editCollection: (string, CollectionEditParams) => void,
isCollectionMine: boolean,
Expand Down Expand Up @@ -145,7 +145,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
hideMenu = false,
// repostUrl,
isLivestream, // need both? CHECK
live,
isLivestreamActive,
collectionId,
collectionIndex,
editCollection,
Expand Down Expand Up @@ -336,7 +336,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
}

let liveProperty = null;
if (live === true) {
if (isLivestreamActive === true) {
liveProperty = (claim) => <>LIVE</>;
}

Expand All @@ -349,7 +349,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
'claim-preview__wrapper--channel': isChannelUri && type !== 'inline',
'claim-preview__wrapper--inline': type === 'inline',
'claim-preview__wrapper--small': type === 'small',
'claim-preview__live': live,
'claim-preview__live': isLivestreamActive,
'claim-preview__active': active,
})}
>
Expand Down Expand Up @@ -386,7 +386,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)}
</div>
{/* @endif */}
{!isLivestream && (
{(!isLivestream || isLivestreamActive) && (
<div className="claim-preview__file-property-overlay">
<PreviewOverlayProperties uri={uri} small={type === 'small'} properties={liveProperty} />
</div>
Expand Down
Loading

0 comments on commit 3b47edc

Please sign in to comment.