lbry-desktop/ui/page/home/view.jsx
infinite-persistence 1e67a5cc7f
[on hold recsys work] Recommended For You (#782)
* Factor out lighthouse-result processing code for FYP re-use.

The FYP results will be in the same format as LH.

* Recsys: add ability to pass in specific uuid to use

For FYP, we want to pass the UUID as a param when searching for recommendations. The search comes before the recsys entry creation, so we need to generate the UUID first when searching, and then tell recsys to use that specific ID.

* Redux: fetch and store FYP

Note that the gid cannot be used as "hash" for the uri list -- it doesn't necessarily change when the list changes, so we can't use it to optimize redux.  For now, just always update/render when re-fetched.

* UI for FYP

* Mark rendered FYPs

* Pass the FYP ID down the same way as Collection ID

Not ideal, but at least it's in the same pattern as existing code for now. The whole prop-drilling problem with the claim components will be fixed together later.

* Include 'gid' and 'uuid' in recommendation search

* Allow users to mark recommendations that they dislike

* Pass auth-token to all FYP requests + remove beacon use

beacons are unreliable and often blocked

* Only show FYP for members

* FYP readme page

* small fixes

* fyp

Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
2022-03-15 15:07:31 -04:00

203 lines
6.4 KiB
JavaScript

// @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import { SITE_NAME, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS } from 'config';
import React, { useState } from 'react';
import Page from 'component/page';
import Button from 'component/button';
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 RecommendedPersonal from 'component/recommendedPersonal';
import { useIsLargeScreen } from 'effects/use-screensize';
import { GetLinksData } from 'util/buildHomepage';
import { getLivestreamUris } from 'util/livestream';
import ScheduledStreams from 'component/scheduledStreams';
import { splitBySeparator } from 'util/lbryURI';
import classnames from 'classnames';
import Ads from 'web/component/ads';
// @if TARGET='web'
import Meme from 'web/component/meme';
// @endif
type Props = {
authenticated: boolean,
followedTags: Array<Tag>,
subscribedChannels: Array<Subscription>,
showNsfw: boolean,
homepageData: any,
activeLivestreams: any,
doFetchActiveLivestreams: () => void,
fetchingActiveLivestreams: boolean,
hideScheduledLivestreams: boolean,
};
function HomePage(props: Props) {
const {
followedTags,
subscribedChannels,
authenticated,
showNsfw,
homepageData,
activeLivestreams,
doFetchActiveLivestreams,
fetchingActiveLivestreams,
hideScheduledLivestreams,
} = 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 isLargeScreen = useIsLargeScreen();
const channelIds = subscribedChannels.map((sub) => splitBySeparator(sub.uri)[1]);
const rowData: Array<RowDataItem> = GetLinksData(
homepageData,
isLargeScreen,
true,
authenticated,
showPersonalizedChannels,
showPersonalizedTags,
subscribedChannels,
followedTags,
showIndividualTags,
showNsfw
);
type SectionHeaderProps = {
title: string,
navigate?: string,
icon?: string,
help?: string,
};
const SectionHeader = ({ title, navigate = '/', icon = '', help }: SectionHeaderProps) => {
return (
<h1 className="claim-grid__header">
<Button navigate={navigate} button="link">
<Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />
<span className="claim-grid__title">{title}</span>
{help}
</Button>
</h1>
);
};
function getRowElements(title, route, link, icon, help, options, index, pinUrls) {
const tilePlaceholder = (
<ul className="claim-grid">
{new Array(options.pageSize || 8).fill(1).map((x, i) => (
<ClaimPreviewTile showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS} key={i} placeholder />
))}
</ul>
);
const claimTiles = (
<ClaimTilesDiscover
{...options}
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
hasSource
prefixUris={getLivestreamUris(activeLivestreams, options.channelIds)}
pinUrls={pinUrls}
injectedItem={
index === 0 && {
node: <Ads small type="video" tileLayout />,
replace: window.odysee_ad_blocker_detected === false,
}
}
/>
);
return (
<div
key={title}
className={classnames('claim-grid__wrapper', {
'show-ribbon': index === 0,
})}
>
{/* category header */}
{index !== 0 && title && typeof title === 'string' && (
<SectionHeader title={__(title)} navigate={route || link} icon={icon} help={help} />
)}
{index === 0 && <>{claimTiles}</>}
{index !== 0 && (
<WaitUntilOnPage name={title} placeholder={tilePlaceholder} yOffset={800}>
{claimTiles}
</WaitUntilOnPage>
)}
{/* view more button */}
{(route || link) && (
<Button
className="claim-grid__title--secondary"
button="link"
navigate={route || link}
iconRight={ICONS.ARROW_RIGHT}
label={__('View More')}
/>
)}
</div>
);
}
React.useEffect(() => {
doFetchActiveLivestreams();
}, []);
const [hasPersonalRecommendations, setHasPersonalRecommendations] = useState(false);
const [hasScheduledStreams, setHasScheduledStreams] = useState(false);
const scheduledStreamsLoaded = (total) => setHasScheduledStreams(total > 0);
return (
<Page className="homePage-wrapper" fullWidthPage>
{!SIMPLE_SITE && (authenticated || !IS_WEB) && !subscribedChannels.length && (
<div className="notice-message">
<h1 className="section__title">
{__("%SITE_NAME% is more fun if you're following channels", { SITE_NAME })}
</h1>
<p className="section__actions">
<Button
button="primary"
navigate={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`}
label={__('Find new channels to follow')}
/>
</p>
</div>
)}
{/* @if TARGET='web' */}
{SIMPLE_SITE && <Meme />}
{/* @endif */}
<RecommendedPersonal onLoad={(displayed) => setHasPersonalRecommendations(displayed)} />
{!fetchingActiveLivestreams && (
<>
{authenticated && channelIds.length > 0 && !hideScheduledLivestreams && (
<ScheduledStreams
channelIds={channelIds}
tileLayout
liveUris={getLivestreamUris(activeLivestreams, channelIds)}
limitClaimsPerChannel={2}
onLoad={scheduledStreamsLoaded}
/>
)}
{authenticated && ((hasScheduledStreams && !hideScheduledLivestreams) || hasPersonalRecommendations) && (
<SectionHeader title={__('Following')} navigate={`/$/${PAGES.CHANNELS_FOLLOWING}`} icon={ICONS.SUBSCRIBE} />
)}
</>
)}
{rowData.map(({ title, route, link, icon, help, pinnedUrls: pinUrls, options = {} }, index) => {
// add pins here
return getRowElements(title, route, link, icon, help, options, index, pinUrls);
})}
</Page>
);
}
export default HomePage;