lbry-desktop/ui/page/discover/view.jsx
infinite-persistence b0e88ff5d1
Add "Hide Repost" button in channel page (#1796)
* Re-organized per 'state->var->func->effect->return' structure.

* Add "Hide Repost" button in channel page

Ticket: 1762

## Change
For the placement of the button, putting it inside the expanded settings group feels the most natural, plus we then don't need to check whether the channel has reposts or not before displaying it (the expanded area is for stuff like this).

## Notes
The tricky part was making the code maintainable w.r.t to the global "Hide Repost" setting. Changed `forceShowReposts` to `hideRepostsOverride`, hopefully makes things more obvious.
- undefined = fallback to global setting
- true/false = use override
2022-07-05 13:20:10 -04:00

281 lines
9.5 KiB
JavaScript

// @flow
import React, { useRef } from 'react';
import classnames from 'classnames';
import { DOMAIN, SIMPLE_SITE } from 'config';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button';
import { ClaimSearchFilterContext } from 'contexts/claimSearchFilterContext';
import useHover from 'effects/use-hover';
import { useIsMobile } from 'effects/use-screensize';
import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw';
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 moment from 'moment';
import LivestreamSection from './livestreamSection';
const CATEGORY_CONTENT_TYPES_FILTER = CS.CONTENT_TYPES.filter((x) => x !== CS.CLAIM_REPOST);
type Props = {
dynamicRouteProps: RowDataItem,
location: { search: string },
followedTags: Array<Tag>,
repostedUri: string,
repostedClaim: ?GenericClaim,
languageSetting: string,
searchInLanguage: boolean,
doToggleTagFollowDesktop: (string) => void,
doResolveUri: (string) => void,
tileLayout: boolean,
activeLivestreams: ?LivestreamInfo,
doFetchActiveLivestreams: (orderBy: ?Array<string>, lang: ?Array<string>) => void,
hasPremiumPlus: boolean,
};
function DiscoverPage(props: Props) {
const {
location: { search },
followedTags,
repostedClaim,
repostedUri,
languageSetting,
searchInLanguage,
doToggleTagFollowDesktop,
doResolveUri,
tileLayout,
activeLivestreams,
doFetchActiveLivestreams,
dynamicRouteProps,
hasPremiumPlus,
} = props;
const buttonRef = useRef();
const isHovering = useHover(buttonRef);
const isMobile = useIsMobile();
const isWildWest = dynamicRouteProps && dynamicRouteProps.id === 'WILD_WEST';
const isCategory = Boolean(dynamicRouteProps);
const urlParams = new URLSearchParams(search);
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
const claimType = urlParams.get('claim_type');
const tagsQuery = urlParams.get('t') || null;
const freshnessParam = urlParams.get(CS.FRESH_KEY);
const orderParam = urlParams.get(CS.ORDER_BY_KEY);
const tags = tagsQuery ? tagsQuery.split(',') : null;
const repostedClaimIsResolved = repostedUri && repostedClaim;
const hideRepostRibbon = isCategory && !isWildWest;
// Eventually allow more than one tag on this page
// Restricting to one to make follow/unfollow simpler
const tag = (tags && tags[0]) || null;
const channelIds = dynamicRouteProps?.options?.channelIds || undefined;
const excludedChannelIds = dynamicRouteProps?.options?.excludedChannelIds || undefined;
const isFollowing = React.useMemo(() => followedTags.some((t) => t.name === tag), [tag, followedTags]);
const filters = { contentTypes: isCategory && !isWildWest ? CATEGORY_CONTENT_TYPES_FILTER : CS.CONTENT_TYPES };
// **************************************************************************
// **************************************************************************
function getMeta() {
if (!dynamicRouteProps) {
return (
<a
className="help"
href="https://odysee.com/@OdyseeHelp:b/trending:50"
title={__('Learn more about Credits on %DOMAIN%', { DOMAIN })}
>
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Results boosted by %lbc%</I18nMessage>
</a>
);
}
// NOTE: the if-block below will never run because a Tag page is also
// "!dynamicRouteProps". However, using the existing channel Follow button
// to follow a tag looks confusing, so I'll leave things as-is for now.
if (tag && !isMobile) {
let label = isFollowing
? __('Following --[button label indicating a channel has been followed]--')
: __('Follow');
if (isHovering && isFollowing) {
label = __('Unfollow');
}
return (
<Button
ref={buttonRef}
button="alt"
icon={ICONS.SUBSCRIBE}
iconColor="red"
onClick={handleFollowClick}
requiresAuth={IS_WEB}
label={label}
/>
);
}
return null;
}
function getSubSection() {
const includeLivestreams = !tagsQuery;
if (includeLivestreams) {
return (
<LivestreamSection
tileLayout={repostedUri ? false : tileLayout}
channelIds={channelIds}
excludedChannelIds={excludedChannelIds}
activeLivestreams={activeLivestreams}
doFetchActiveLivestreams={doFetchActiveLivestreams}
searchLanguages={dynamicRouteProps?.options?.searchLanguages}
languageSetting={languageSetting}
searchInLanguage={searchInLanguage}
langParam={langParam}
/>
);
}
return null;
}
function getPins(routeProps) {
if (routeProps && (routeProps.pinnedUrls || routeProps.pinnedClaimIds)) {
return {
urls: routeProps.pinnedUrls,
claimIds: routeProps.pinnedClaimIds,
onlyPinForOrder: CS.ORDER_BY_TRENDING,
};
}
}
function getHeaderLabel() {
let headerLabel;
if (repostedClaim) {
headerLabel = __('Reposts of %uri%', { uri: repostedUri });
} else if (tag) {
headerLabel = (
<span>
<Icon icon={ICONS.TAG} size={10} />
{(tag === CS.TAGS_ALL && __('All Content')) || (tag === CS.TAGS_FOLLOWED && __('Followed Tags')) || tag}
<Button
className="claim-search__tags-link"
button="link"
label={__('Manage Tags')}
navigate={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`}
/>
</span>
);
} else {
headerLabel = (
<span>
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.DISCOVER} size={10} />
{(dynamicRouteProps && __(`${dynamicRouteProps.title}`)) || __('All Content')}
</span>
);
}
return headerLabel;
}
function getAds() {
return (
!isWildWest &&
!hasPremiumPlus && {
node: (index, lastVisibleIndex, pageSize) => {
if (pageSize && index < pageSize) {
return index === lastVisibleIndex ? <Ads small type="video" tileLayout={tileLayout} /> : null;
} else {
return index % (pageSize * 2) === 0 ? <Ads small type="video" tileLayout={tileLayout} /> : null;
}
},
}
);
}
function getReleaseTime() {
const categoryReleaseTime = dynamicRouteProps?.options?.releaseTime;
if (isWildWest) {
// The homepage definition currently does not support 'start-of-week', so
// continue to hardcode here for now.
return `>${Math.floor(moment().subtract(0, 'hour').startOf('week').unix())}`;
} else if (categoryReleaseTime) {
const hasFreshnessOverride = orderParam === CS.ORDER_BY_TOP && freshnessParam !== null;
if (!hasFreshnessOverride) {
return categoryReleaseTime;
}
}
return undefined;
}
function handleFollowClick() {
if (tag) {
doToggleTagFollowDesktop(tag);
const nowFollowing = !isFollowing;
analytics.tagFollowEvent(tag, nowFollowing, 'tag-page');
}
}
// **************************************************************************
// **************************************************************************
React.useEffect(() => {
if (repostedUri && !repostedClaimIsResolved) {
doResolveUri(repostedUri);
}
}, [repostedUri, repostedClaimIsResolved, doResolveUri]);
// **************************************************************************
// **************************************************************************
return (
<Page
noFooter
fullWidthPage={tileLayout}
className={classnames('main__discover', { 'hide-ribbon': hideRepostRibbon })}
>
<ClaimSearchFilterContext.Provider value={filters}>
<ClaimListDiscover
pins={getPins(dynamicRouteProps)}
hideFilters={isWildWest ? true : undefined}
header={repostedUri ? <span /> : undefined}
subSection={getSubSection()}
tileLayout={repostedUri ? false : tileLayout}
defaultOrderBy={isWildWest || tags ? CS.ORDER_BY_TRENDING : undefined}
claimType={claimType ? [claimType] : undefined}
defaultStreamType={isCategory && !isWildWest ? [CS.FILE_VIDEO, CS.FILE_AUDIO, CS.FILE_DOCUMENT] : undefined}
headerLabel={getHeaderLabel()}
tags={tags}
hiddenNsfwMessage={<HiddenNsfw type="page" />}
repostedClaimId={repostedClaim ? repostedClaim.claim_id : null}
injectedItem={getAds()}
// TODO: find a better way to determine discover / wild west vs other modes release times
// for now including && !tags so that
releaseTime={getReleaseTime()}
feeAmount={isWildWest || tags ? CS.FEE_AMOUNT_ANY : undefined}
channelIds={channelIds}
excludedChannelIds={excludedChannelIds}
limitClaimsPerChannel={
SIMPLE_SITE
? (dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.limitClaimsPerChannel) || 3
: 3
}
meta={getMeta()}
hasSource
hideRepostsOverride={dynamicRouteProps ? true : undefined}
searchLanguages={dynamicRouteProps?.options?.searchLanguages}
/>
</ClaimSearchFilterContext.Provider>
</Page>
);
}
export default DiscoverPage;