Make Wild-West a proper Category + additional features. (1353)

This commit is contained in:
infinite-persistence 2022-04-20 22:57:35 +08:00
commit 8e2ac343b0
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
12 changed files with 88 additions and 60 deletions

View file

@ -22,9 +22,11 @@ declare type RowDataItem = {
pinnedClaimIds?: Array<string>, // takes precedence over pinnedUrls
options?: {
channelIds?: Array<string>,
excludedChannelIds?: Array<string>,
limitClaimsPerChannel?: number,
pageSize?: number,
releaseTime?: string,
searchLanguages?: Array<string>,
},
route?: string,
hideForUnauth?: boolean,

View file

@ -44,7 +44,8 @@ type Props = {
showHiddenByUser?: boolean,
showNoSourceClaims?: boolean,
tileLayout: boolean,
ignoreSearchInLanguage?: boolean,
searchLanguages?: Array<string>,
ignoreSearchInLanguage?: boolean, // Negate the redux setting where it doesn't make sense.
orderBy?: Array<string>, // Trending, New, Top
defaultOrderBy?: string,
@ -70,6 +71,7 @@ type Props = {
limitClaimsPerChannel?: number,
channelIds?: Array<string>,
excludedChannelIds?: Array<string>,
claimIds?: Array<string>,
subscribedChannels: Array<Subscription>,
@ -127,6 +129,7 @@ function ClaimListDiscover(props: Props) {
meta,
subSection,
channelIds,
excludedChannelIds,
showNsfw,
hideReposts,
fetchViewCount,
@ -165,6 +168,7 @@ function ClaimListDiscover(props: Props) {
maxPages,
forceShowReposts = false,
languageSetting,
searchLanguages,
searchInLanguage,
ignoreSearchInLanguage,
limitClaimsPerChannel,
@ -211,9 +215,15 @@ function ClaimListDiscover(props: Props) {
new Set(mutedUris.concat(blockedUris).map((uri) => splitBySeparator(uri)[1]))
);
// Precedence:
// - searchLanguages (per instance attribute)
// - urlParams
// - languageSetting (redux setting)
const language = searchLanguages ? searchLanguages.join(',') : languageSetting;
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
const searchInSelectedLangOnly = searchInLanguage && !ignoreSearchInLanguage;
const languageParams = resolveLangForClaimSearch(languageSetting, searchInSelectedLangOnly, langParam);
const forcedSearchInLanguage = Boolean(searchLanguages);
const userSearchInLanguage = searchInLanguage && !ignoreSearchInLanguage;
const languageParams = resolveLangForClaimSearch(language, forcedSearchInLanguage || userSearchInLanguage, langParam);
let claimTypeParam = claimType || defaultClaimType || null;
let streamTypeParam = streamType || defaultStreamType || null;
@ -257,6 +267,7 @@ function ClaimListDiscover(props: Props) {
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
const excludedIdsParam = excludedChannelIds;
const feeAmountParam = urlParams.get('fee_amount') || feeAmount;
// const originalPageSize = pageSize || CS.PAGE_SIZE;
const originalPageSize = 12;
@ -346,6 +357,10 @@ function ClaimListDiscover(props: Props) {
options.channel_ids = channelIdsParam;
}
if (excludedIdsParam) {
options.not_channel_ids = (options.not_channel_ids || []).concat(excludedIdsParam);
}
if (tagsParam) {
if (tagsParam !== CS.TAGS_ALL && tagsParam !== '') {
if (tagsParam === CS.TAGS_FOLLOWED) {
@ -694,7 +709,7 @@ function ClaimListDiscover(props: Props) {
<div className="section__header--actions">
<div className="section__actions">
{headerToUse}
{searchInSelectedLangOnly && <LangFilterIndicator />}
{userSearchInLanguage && <LangFilterIndicator />}
</div>
{meta && <div className="section__actions--no-margin">{meta}</div>}
</div>
@ -734,7 +749,7 @@ function ClaimListDiscover(props: Props) {
<div className="section__header--actions">
<div className="section__actions">
{headerToUse}
{searchInSelectedLangOnly && <LangFilterIndicator />}
{userSearchInLanguage && <LangFilterIndicator />}
</div>
{meta && <div className="section__actions--no-margin">{meta}</div>}
</div>

View file

@ -2,6 +2,7 @@
import React, { useState } from 'react';
import classnames from 'classnames';
import Icon from 'component/common/icon';
import { HOMEPAGE_EXCLUDED_CATEGORIES } from 'constants/homepage_languages';
import * as ICONS from 'constants/icons';
import 'scss/component/homepage-sort.scss';
@ -48,26 +49,26 @@ function getInitialList(listId, savedOrder, homepageSections) {
const savedHiddenOrder = savedOrder.hidden || [];
const sectionKeys = Object.keys(homepageSections);
if (sectionKeys.includes('NEWS') && !savedHiddenOrder.includes('NEWS') && !savedActiveOrder.includes('NEWS')) {
savedHiddenOrder.push('NEWS');
}
if (listId === 'ACTIVE') {
// Start with saved order, excluding obsolete items (i.e. category removed or not available in non-English)
const finalOrder = savedActiveOrder.filter((x) => sectionKeys.includes(x));
// Add new items (e.g. new categories)
// Add new categories not seen previously.
sectionKeys.forEach((x) => {
if (!finalOrder.includes(x)) {
finalOrder.push(x);
}
});
// Exclude items that were moved to Hidden
return finalOrder.filter((x) => !savedHiddenOrder.includes(x));
// Exclude items that were moved to Hidden, or intentionally excluded from Homepage.
return finalOrder
.filter((x) => !savedHiddenOrder.includes(x))
.filter((x) => !HOMEPAGE_EXCLUDED_CATEGORIES.includes(x));
} else {
console.assert(listId === 'HIDDEN', `Unhandled listId: ${listId}`);
return savedHiddenOrder.filter((x) => sectionKeys.includes(x));
return savedHiddenOrder
.filter((x) => sectionKeys.includes(x))
.filter((x) => !HOMEPAGE_EXCLUDED_CATEGORIES.includes(x));
}
}

View file

@ -190,21 +190,19 @@ function AppRouter(props: Props) {
const tagParams = urlParams.get(CS.TAGS_KEY);
const isLargeScreen = useIsLargeScreen();
const homeCategoryPages = React.useMemo(() => {
const categoryPages = React.useMemo(() => {
const dynamicRoutes = GetLinksData(homepageData, isLargeScreen).filter(
(potentialRoute: any) => potentialRoute && potentialRoute.route
(x: any) => x && x.route && (x.id !== 'WILD_WEST' || !wildWestDisabled)
);
return dynamicRoutes.map((dynamicRouteProps: RowDataItem) => (
<Route
key={dynamicRouteProps.route}
path={dynamicRouteProps.route}
component={(routerProps) => (
<DiscoverPage {...routerProps} dynamicRouteProps={dynamicRouteProps} hideRepostRibbon />
)}
component={(routerProps) => <DiscoverPage {...routerProps} dynamicRouteProps={dynamicRouteProps} />}
/>
));
}, [homepageData, isLargeScreen]);
}, [homepageData, isLargeScreen, wildWestDisabled]);
// For people arriving at settings page from deeplinks, know whether they can "go back"
useEffect(() => {
@ -291,8 +289,7 @@ function AppRouter(props: Props) {
<Route path={`/`} exact component={HomePage} />
{(!wildWestDisabled || tagParams) && <Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />}
{!wildWestDisabled && <Route path={`/$/${PAGES.WILD_WEST}`} exact component={DiscoverPage} />}
{homeCategoryPages}
{categoryPages}
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} />

View file

@ -5,7 +5,7 @@ import { doClearClaimSearch } from 'redux/actions/claims';
import { doClearPurchasedUriSuccess } from 'redux/actions/file';
import { selectFollowedTags } from 'redux/selectors/tags';
import { selectUserVerifiedEmail, selectUser, selectOdyseeMembershipName } from 'redux/selectors/user';
import { selectHomepageData, selectWildWestDisabled } from 'redux/selectors/settings';
import { selectHomepageData } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
import { selectPurchaseUriSuccess, selectOdyseeMembershipForUri } from 'redux/selectors/claims';
@ -21,7 +21,6 @@ const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state),
user: selectUser(state),
homepageData: selectHomepageData(state),
wildWestDisabled: selectWildWestDisabled(state),
odyseeMembership: selectOdyseeMembershipName(state),
odyseeMembershipByUri: (uri) => selectOdyseeMembershipForUri(state, uri),
});

View file

@ -118,12 +118,6 @@ const UNAUTH_LINKS: Array<SideNavLink> = [
},
];
const WILD_WEST: SideNavLink = {
title: 'Wild West',
link: `/$/${PAGES.WILD_WEST}`,
icon: ICONS.WILD_WEST,
};
// ****************************************************************************
// ****************************************************************************
@ -143,7 +137,6 @@ type Props = {
doClearPurchasedUriSuccess: () => void,
user: ?User,
homepageData: any,
wildWestDisabled: boolean,
doClearClaimSearch: () => void,
odyseeMembership: string,
odyseeMembershipByUri: (uri: string) => string,
@ -166,7 +159,6 @@ function SideNavigation(props: Props) {
homepageData,
user,
followedTags,
wildWestDisabled,
doClearClaimSearch,
odyseeMembership,
odyseeMembershipByUri,
@ -549,7 +541,6 @@ function SideNavigation(props: Props) {
<>
{/* $FlowFixMe: GetLinksData type needs an update */}
{EXTRA_SIDEBAR_LINKS.map((linkProps) => getLink(linkProps))}
{!wildWestDisabled && getLink(WILD_WEST)}
</>
)}
</ul>

View file

@ -19,3 +19,5 @@ export function getHomepageLanguage(code) {
}
export default HOMEPAGE_LANGUAGES;
export const HOMEPAGE_EXCLUDED_CATEGORIES = Object.freeze(['NEWS', 'WILD_WEST']);

View file

@ -8,7 +8,7 @@ import ClaimListDiscover from 'component/claimListDiscover';
import { useIsMobile, useIsLargeScreen } from 'effects/use-screensize';
import usePersistedState from 'effects/use-persisted-state';
import { getLivestreamUris } from 'util/livestream';
import { resolveLangForClaimSearch } from '../../util/default-languages';
import { resolveLangForClaimSearch } from 'util/default-languages';
const DEFAULT_LIVESTREAM_TILE_LIMIT = 8;
const SECTION = Object.freeze({ COLLAPSED: 1, EXPANDED: 2 });
@ -23,8 +23,10 @@ function getTileLimit(isLargeScreen, originalSize) {
type Props = {
tileLayout: boolean,
channelIds?: Array<string>,
excludedChannelIds?: Array<string>,
activeLivestreams: ?LivestreamInfo,
doFetchActiveLivestreams: (orderBy: ?Array<string>, lang: ?Array<string>) => void,
searchLanguages?: Array<string>,
languageSetting?: string,
searchInLanguage?: boolean,
langParam?: string | null,
@ -34,8 +36,10 @@ export default function LivestreamSection(props: Props) {
const {
tileLayout,
channelIds,
excludedChannelIds,
activeLivestreams,
doFetchActiveLivestreams,
searchLanguages,
languageSetting,
searchInLanguage,
langParam,
@ -49,7 +53,7 @@ export default function LivestreamSection(props: Props) {
const initialLiveTileLimit = getTileLimit(isLargeScreen, DEFAULT_LIVESTREAM_TILE_LIMIT);
const [liveSection, setLiveSection] = React.useState(liveSectionStore || SECTION.COLLAPSED);
const livestreamUris = getLivestreamUris(activeLivestreams, channelIds);
const livestreamUris = getLivestreamUris(activeLivestreams, channelIds, excludedChannelIds);
const liveTilesOverLimit = livestreamUris && livestreamUris.length > initialLiveTileLimit;
function collapseSection() {
@ -66,7 +70,9 @@ export default function LivestreamSection(props: Props) {
React.useEffect(() => {
// Fetch active livestreams on mount
const langCsv = resolveLangForClaimSearch(languageSetting, searchInLanguage, langParam);
const language = searchLanguages ? searchLanguages.join(',') : languageSetting;
const searchInSelectedLangOnly = Boolean(searchLanguages) || searchInLanguage;
const langCsv = resolveLangForClaimSearch(language, searchInSelectedLangOnly, langParam);
const lang = langCsv ? langCsv.split(',') : null;
doFetchActiveLivestreams(CS.ORDER_BY_NEW_VALUE, lang);
// eslint-disable-next-line react-hooks/exhaustive-deps, (on mount only)

View file

@ -25,7 +25,6 @@ type Props = {
followedTags: Array<Tag>,
repostedUri: string,
repostedClaim: ?GenericClaim,
hideRepostRibbon?: boolean,
languageSetting: string,
searchInLanguage: boolean,
doToggleTagFollowDesktop: (string) => void,
@ -41,7 +40,6 @@ function DiscoverPage(props: Props) {
followedTags,
repostedClaim,
repostedUri,
hideRepostRibbon,
languageSetting,
searchInLanguage,
doToggleTagFollowDesktop,
@ -55,7 +53,7 @@ function DiscoverPage(props: Props) {
const buttonRef = useRef();
const isHovering = useHover(buttonRef);
const isMobile = useIsMobile();
const isWildWest = window.location.pathname === `/$/${PAGES.WILD_WEST}`;
const isWildWest = dynamicRouteProps && dynamicRouteProps.id === 'WILD_WEST';
const urlParams = new URLSearchParams(search);
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
@ -63,14 +61,13 @@ function DiscoverPage(props: Props) {
const tagsQuery = urlParams.get('t') || null;
const tags = tagsQuery ? tagsQuery.split(',') : null;
const repostedClaimIsResolved = repostedUri && repostedClaim;
const hideRepostRibbon = !isWildWest;
const discoverIcon = SIMPLE_SITE ? ICONS.WILD_WEST : ICONS.DISCOVER;
const discoverLabel = SIMPLE_SITE ? __('Wild West') : __('All Content');
// 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 && dynamicRouteProps.options && dynamicRouteProps.options.channelIds) || undefined;
const channelIds = dynamicRouteProps?.options?.channelIds || undefined;
const excludedChannelIds = dynamicRouteProps?.options?.excludedChannelIds || undefined;
const isFollowing = followedTags.map(({ name }) => name).includes(tag);
let label = isFollowing ? __('Following --[button label indicating a channel has been followed]--') : __('Follow');
@ -116,8 +113,10 @@ function DiscoverPage(props: Props) {
<LivestreamSection
tileLayout={repostedUri ? false : tileLayout}
channelIds={channelIds}
excludedChannelIds={excludedChannelIds}
activeLivestreams={activeLivestreams}
doFetchActiveLivestreams={doFetchActiveLivestreams}
searchLanguages={dynamicRouteProps?.options?.searchLanguages}
languageSetting={languageSetting}
searchInLanguage={searchInLanguage}
langParam={langParam}
@ -172,15 +171,15 @@ function DiscoverPage(props: Props) {
} else {
headerLabel = (
<span>
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || discoverIcon} size={10} />
{(dynamicRouteProps && __(`${dynamicRouteProps.title}`)) || discoverLabel}
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.DISCOVER} size={10} />
{(dynamicRouteProps && __(`${dynamicRouteProps.title}`)) || __('All Content')}
</span>
);
}
let releaseTime =
dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.releaseTime
? dynamicRouteProps.options.releaseTime
: !dynamicRouteProps && !tags && `>${Math.floor(moment().subtract(0, 'hour').startOf('week').unix())}`;
: !isWildWest && `>${Math.floor(moment().subtract(0, 'hour').startOf('week').unix())}`;
return (
<Page
@ -190,11 +189,11 @@ function DiscoverPage(props: Props) {
>
<ClaimListDiscover
pins={getPins(dynamicRouteProps)}
hideFilters={SIMPLE_SITE ? !(dynamicRouteProps || tags) : undefined}
hideFilters={isWildWest ? true : undefined}
header={repostedUri ? <span /> : undefined}
subSection={getSubSection()}
tileLayout={repostedUri ? false : tileLayout}
defaultOrderBy={SIMPLE_SITE ? (dynamicRouteProps ? undefined : CS.ORDER_BY_TRENDING) : undefined}
defaultOrderBy={isWildWest || tags ? CS.ORDER_BY_TRENDING : undefined}
claimType={claimType ? [claimType] : undefined}
headerLabel={headerLabel}
tags={tags}
@ -207,8 +206,9 @@ function DiscoverPage(props: Props) {
// TODO: find a better way to determine discover / wild west vs other modes release times
// for now including && !tags so that
releaseTime={releaseTime || undefined}
feeAmount={SIMPLE_SITE ? !dynamicRouteProps && CS.FEE_AMOUNT_ANY : undefined}
feeAmount={isWildWest || tags ? CS.FEE_AMOUNT_ANY : undefined}
channelIds={channelIds}
excludedChannelIds={excludedChannelIds}
limitClaimsPerChannel={
SIMPLE_SITE
? (dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.limitClaimsPerChannel) || 3
@ -217,6 +217,7 @@ function DiscoverPage(props: Props) {
meta={getMeta()}
hasSource
forceShowReposts={dynamicRouteProps}
searchLanguages={dynamicRouteProps?.options?.searchLanguages}
/>
</Page>
);

View file

@ -84,7 +84,6 @@ function HomePage(props: Props) {
showNsfw
);
// TODO: probably need memo, or incorporate into GetLinksData.
let sortedRowData: Array<RowDataItem> = [];
if (homepageOrder.active && authenticated) {
homepageOrder.active.forEach((key) => {
@ -99,7 +98,7 @@ function HomePage(props: Props) {
if (homepageOrder.hidden) {
rowData.forEach((data: RowDataItem) => {
// $FlowIssue: null 'hidden' already avoided, but flow can't see beyond this anonymous function?
// $FlowIssue: null 'hidden' already avoided outside anonymous function.
if (!homepageOrder.hidden.includes(data.id)) {
sortedRowData.push(data);
}
@ -107,14 +106,9 @@ function HomePage(props: Props) {
}
} else {
rowData.forEach((key) => {
// always inject FYP if homepage not customized, hide news.
if (key.id === 'FOLLOWING') {
sortedRowData.push(key);
if (hasMembership) {
sortedRowData.push(FYP_SECTION);
}
} else if (key.id !== 'NEWS') {
sortedRowData.push(key);
sortedRowData.push(key);
if (key.id === 'FOLLOWING' && hasMembership) {
sortedRowData.push(FYP_SECTION);
}
});
}

View file

@ -2,6 +2,7 @@
import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons';
import * as CS from 'constants/claim_search';
import { HOMEPAGE_EXCLUDED_CATEGORIES } from 'constants/homepage_languages';
import { parseURI } from 'util/lbryURI';
import moment from 'moment';
import { toCapitalCase } from 'util/string';
@ -20,6 +21,8 @@ export type HomepageCat = {
tags?: Array<string>,
pinnedUrls?: Array<string>,
pinnedClaimIds?: Array<string>, // takes precedence over pinnedUrls
excludedChannelIds?: Array<string>,
searchLanguages?: Array<string>,
mixIn?: Array<string>,
};
@ -94,9 +97,11 @@ export const getHomepageRowForCat = (key: string, cat: HomepageCat) => {
options: {
claimType: cat.claimType || ['stream', 'repost'],
channelIds: cat.channelIds,
excludedChannelIds: cat.excludedChannelIds,
orderBy: orderValue,
pageSize: cat.pageSize || undefined,
limitClaimsPerChannel: limitClaims,
searchLanguages: cat.searchLanguages,
releaseTime: `>${Math.floor(
moment()
.subtract(cat.daysOfContent || 30, 'days')
@ -110,7 +115,7 @@ export const getHomepageRowForCat = (key: string, cat: HomepageCat) => {
export function GetLinksData(
all: any, // HomepageData type?
isLargeScreen: boolean,
isHomepage?: boolean = false,
isHomepage?: boolean,
authenticated?: boolean,
showPersonalizedChannels?: boolean,
showPersonalizedTags?: boolean,
@ -338,6 +343,11 @@ export function GetLinksData(
const key = entries[i][0];
const val = entries[i][1];
// $FlowFixMe https://github.com/facebook/flow/issues/2221
if (isHomepage && HOMEPAGE_EXCLUDED_CATEGORIES.includes(key)) {
continue;
}
// $FlowFixMe https://github.com/facebook/flow/issues/2221
rowData.push(getHomepageRowForCat(key, val));
}

View file

@ -34,9 +34,14 @@ type StreamData = {
*
* @param activeLivestreams Object obtained from `selectActiveLivestreams`.
* @param channelIds List of channel IDs to filter the results with.
* @param excludedChannelIds
* @returns {[]|Array<*>}
*/
export function getLivestreamUris(activeLivestreams: ?LivestreamInfo, channelIds: ?Array<string>) {
export function getLivestreamUris(
activeLivestreams: ?LivestreamInfo,
channelIds: ?Array<string>,
excludedChannelIds?: Array<string>
) {
let values = (activeLivestreams && Object.values(activeLivestreams)) || [];
if (channelIds && channelIds.length > 0) {
@ -47,6 +52,11 @@ export function getLivestreamUris(activeLivestreams: ?LivestreamInfo, channelIds
values = values.filter((v) => Boolean(v.claimUri));
}
if (excludedChannelIds) {
// $FlowFixMe
values = values.filter((v) => !excludedChannelIds.includes(v.creatorId));
}
// $FlowFixMe
return values.map((v) => v.claimUri);
}