diff --git a/flow-typed/homepage.js b/flow-typed/homepage.js index aed4e747c..211044b88 100644 --- a/flow-typed/homepage.js +++ b/flow-typed/homepage.js @@ -22,9 +22,11 @@ declare type RowDataItem = { pinnedClaimIds?: Array, // takes precedence over pinnedUrls options?: { channelIds?: Array, + excludedChannelIds?: Array, limitClaimsPerChannel?: number, pageSize?: number, releaseTime?: string, + searchLanguages?: Array, }, route?: string, hideForUnauth?: boolean, diff --git a/ui/component/claimListDiscover/view.jsx b/ui/component/claimListDiscover/view.jsx index 420cac9c3..556b54f5d 100644 --- a/ui/component/claimListDiscover/view.jsx +++ b/ui/component/claimListDiscover/view.jsx @@ -44,7 +44,8 @@ type Props = { showHiddenByUser?: boolean, showNoSourceClaims?: boolean, tileLayout: boolean, - ignoreSearchInLanguage?: boolean, + searchLanguages?: Array, + ignoreSearchInLanguage?: boolean, // Negate the redux setting where it doesn't make sense. orderBy?: Array, // Trending, New, Top defaultOrderBy?: string, @@ -70,6 +71,7 @@ type Props = { limitClaimsPerChannel?: number, channelIds?: Array, + excludedChannelIds?: Array, claimIds?: Array, subscribedChannels: Array, @@ -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) {
{headerToUse} - {searchInSelectedLangOnly && } + {userSearchInLanguage && }
{meta &&
{meta}
}
@@ -734,7 +749,7 @@ function ClaimListDiscover(props: Props) {
{headerToUse} - {searchInSelectedLangOnly && } + {userSearchInLanguage && }
{meta &&
{meta}
}
diff --git a/ui/component/homepageSort/view.jsx b/ui/component/homepageSort/view.jsx index f81f933b3..86bce0011 100644 --- a/ui/component/homepageSort/view.jsx +++ b/ui/component/homepageSort/view.jsx @@ -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)); } } diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 1ca39fcf2..5d8cee40c 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -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) => ( ( - - )} + component={(routerProps) => } /> )); - }, [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) { {(!wildWestDisabled || tagParams) && } - {!wildWestDisabled && } - {homeCategoryPages} + {categoryPages} diff --git a/ui/component/sideNavigation/index.js b/ui/component/sideNavigation/index.js index c0dc75104..c01f71996 100644 --- a/ui/component/sideNavigation/index.js +++ b/ui/component/sideNavigation/index.js @@ -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), }); diff --git a/ui/component/sideNavigation/view.jsx b/ui/component/sideNavigation/view.jsx index 2ed1a6923..318150348 100644 --- a/ui/component/sideNavigation/view.jsx +++ b/ui/component/sideNavigation/view.jsx @@ -118,12 +118,6 @@ const UNAUTH_LINKS: Array = [ }, ]; -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)} )} diff --git a/ui/constants/homepage_languages.js b/ui/constants/homepage_languages.js index d67f55aec..eb7d065ac 100644 --- a/ui/constants/homepage_languages.js +++ b/ui/constants/homepage_languages.js @@ -19,3 +19,5 @@ export function getHomepageLanguage(code) { } export default HOMEPAGE_LANGUAGES; + +export const HOMEPAGE_EXCLUDED_CATEGORIES = Object.freeze(['NEWS', 'WILD_WEST']); diff --git a/ui/page/discover/livestreamSection.jsx b/ui/page/discover/livestreamSection.jsx index 82fd35d14..e6dd41c9a 100644 --- a/ui/page/discover/livestreamSection.jsx +++ b/ui/page/discover/livestreamSection.jsx @@ -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, + excludedChannelIds?: Array, activeLivestreams: ?LivestreamInfo, doFetchActiveLivestreams: (orderBy: ?Array, lang: ?Array) => void, + searchLanguages?: Array, 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) diff --git a/ui/page/discover/view.jsx b/ui/page/discover/view.jsx index bfc3f8955..e37ba35e8 100644 --- a/ui/page/discover/view.jsx +++ b/ui/page/discover/view.jsx @@ -25,7 +25,6 @@ type Props = { followedTags: Array, 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) { - - {(dynamicRouteProps && __(`${dynamicRouteProps.title}`)) || discoverLabel} + + {(dynamicRouteProps && __(`${dynamicRouteProps.title}`)) || __('All Content')} ); } 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 ( : 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} /> ); diff --git a/ui/page/home/view.jsx b/ui/page/home/view.jsx index 2dfca7e48..a8edd0dd8 100644 --- a/ui/page/home/view.jsx +++ b/ui/page/home/view.jsx @@ -84,7 +84,6 @@ function HomePage(props: Props) { showNsfw ); - // TODO: probably need memo, or incorporate into GetLinksData. let sortedRowData: Array = []; 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); } }); } diff --git a/ui/util/buildHomepage.js b/ui/util/buildHomepage.js index 3cedf816f..38ba6205f 100644 --- a/ui/util/buildHomepage.js +++ b/ui/util/buildHomepage.js @@ -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, pinnedUrls?: Array, pinnedClaimIds?: Array, // takes precedence over pinnedUrls + excludedChannelIds?: Array, + searchLanguages?: Array, mixIn?: Array, }; @@ -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)); } diff --git a/ui/util/livestream.js b/ui/util/livestream.js index 129c741cd..be9546456 100644 --- a/ui/util/livestream.js +++ b/ui/util/livestream.js @@ -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) { +export function getLivestreamUris( + activeLivestreams: ?LivestreamInfo, + channelIds: ?Array, + excludedChannelIds?: Array +) { 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); }