diff --git a/static/app-strings.json b/static/app-strings.json index ff5f50c5b..728349365 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2170,6 +2170,7 @@ "Starting Soon": "Starting Soon", "Streaming Now": "Streaming Now", "Live": "Live", + "Livestreams": "Livestreams", "Upcoming Livestreams": "Upcoming Livestreams", "Show more upcoming livestreams": "Show more upcoming livestreams", "Show less upcoming livestreams": "Show less upcoming livestreams", diff --git a/ui/page/discover/livestreamSection.jsx b/ui/page/discover/livestreamSection.jsx new file mode 100644 index 000000000..82fd35d14 --- /dev/null +++ b/ui/page/discover/livestreamSection.jsx @@ -0,0 +1,159 @@ +// @flow +import React from 'react'; +import Button from 'component/button'; +import { ENABLE_NO_SOURCE_CLAIMS } from 'config'; +import * as CS from 'constants/claim_search'; +import * as ICONS from 'constants/icons'; +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'; + +const DEFAULT_LIVESTREAM_TILE_LIMIT = 8; +const SECTION = Object.freeze({ COLLAPSED: 1, EXPANDED: 2 }); + +function getTileLimit(isLargeScreen, originalSize) { + return isLargeScreen ? originalSize * (3 / 2) : originalSize; +} + +// **************************************************************************** +// **************************************************************************** + +type Props = { + tileLayout: boolean, + channelIds?: Array, + activeLivestreams: ?LivestreamInfo, + doFetchActiveLivestreams: (orderBy: ?Array, lang: ?Array) => void, + languageSetting?: string, + searchInLanguage?: boolean, + langParam?: string | null, +}; + +export default function LivestreamSection(props: Props) { + const { + tileLayout, + channelIds, + activeLivestreams, + doFetchActiveLivestreams, + languageSetting, + searchInLanguage, + langParam, + } = props; + + const [liveSectionStore, setLiveSectionStore] = usePersistedState('discover:lsSection', SECTION.COLLAPSED); + const [expandedYPos, setExpandedYPos] = React.useState(null); + + const isMobile = useIsMobile(); + const isLargeScreen = useIsLargeScreen(); + + const initialLiveTileLimit = getTileLimit(isLargeScreen, DEFAULT_LIVESTREAM_TILE_LIMIT); + const [liveSection, setLiveSection] = React.useState(liveSectionStore || SECTION.COLLAPSED); + const livestreamUris = getLivestreamUris(activeLivestreams, channelIds); + const liveTilesOverLimit = livestreamUris && livestreamUris.length > initialLiveTileLimit; + + function collapseSection() { + window.scrollTo(0, 0); + setLiveSection(SECTION.COLLAPSED); + } + + React.useEffect(() => { + // Sync liveSection --> liveSectionStore + if (liveSection !== liveSectionStore) { + setLiveSectionStore(liveSection); + } + }, [liveSection, setLiveSectionStore, liveSectionStore]); + + React.useEffect(() => { + // Fetch active livestreams on mount + const langCsv = resolveLangForClaimSearch(languageSetting, searchInLanguage, 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) + }, []); + + React.useEffect(() => { + // Maintain y-position when expanding livestreams section: + if (liveSection === SECTION.EXPANDED && expandedYPos !== null) { + window.scrollTo(0, expandedYPos); + setExpandedYPos(null); + } + }, [liveSection, expandedYPos]); + + if (!livestreamUris || livestreamUris.length === 0) { + return null; + } + + if (isMobile) { + return ( +
+ 1 ? true : tileLayout} + swipeLayout={livestreamUris.length > 1} + headerLabel={
{__('Livestreams')}
} + useSkeletonScreen={false} + showHeader={false} + hideFilters + infiniteScroll={false} + loading={false} + showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS} + /> +
+ ); + } + + return ( +
+ {liveTilesOverLimit && liveSection === SECTION.EXPANDED && ( +
+
+ )} + + + + {liveTilesOverLimit && liveSection === SECTION.COLLAPSED && ( +
+
+ )} + + {liveTilesOverLimit && liveSection === SECTION.EXPANDED && ( +
+
+ )} +
+ ); +} diff --git a/ui/page/discover/view.jsx b/ui/page/discover/view.jsx index e213cb1de..e9a9e7553 100644 --- a/ui/page/discover/view.jsx +++ b/ui/page/discover/view.jsx @@ -1,15 +1,14 @@ // @flow -import { SHOW_ADS, DOMAIN, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS } from 'config'; +import { SHOW_ADS, 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 React, { useState, useRef } from 'react'; +import React, { useRef } from 'react'; import Page from 'component/page'; import ClaimListDiscover from 'component/claimListDiscover'; import Button from 'component/button'; import useHover from 'effects/use-hover'; -import { useIsMobile, useIsLargeScreen } from 'effects/use-screensize'; -import usePersistedState from 'effects/use-persisted-state'; +import { useIsMobile } from 'effects/use-screensize'; import analytics from 'analytics'; import HiddenNsfw from 'component/common/hidden-nsfw'; import Icon from 'component/common/icon'; @@ -17,15 +16,10 @@ import Ads, { injectAd } from 'web/component/ads'; import LbcSymbol from 'component/common/lbc-symbol'; import I18nMessage from 'component/i18nMessage'; import moment from 'moment'; -import { resolveLangForClaimSearch } from 'util/default-languages'; -import { getLivestreamUris } from 'util/livestream'; - -const DEFAULT_LIVESTREAM_TILE_LIMIT = 8; -const SECTION = Object.freeze({ HIDDEN: 0, LESS: 1, MORE: 2 }); +import LivestreamSection from './livestreamSection'; type Props = { dynamicRouteProps: RowDataItem, - // --- redux --- location: { search: string }, followedTags: Array, repostedUri: string, @@ -57,13 +51,9 @@ function DiscoverPage(props: Props) { dynamicRouteProps, } = props; - const [liveSectionStore, setLiveSectionStore] = usePersistedState('discover:liveSection', SECTION.LESS); - const [expandedYPos, setExpandedYPos] = useState(null); - const buttonRef = useRef(); const isHovering = useHover(buttonRef); const isMobile = useIsMobile(); - const isLargeScreen = useIsLargeScreen(); const urlParams = new URLSearchParams(search); const langParam = urlParams.get(CS.LANGUAGE_KEY) || null; @@ -86,57 +76,53 @@ function DiscoverPage(props: Props) { label = __('Unfollow'); } - const initialLiveTileLimit = getPageSize(DEFAULT_LIVESTREAM_TILE_LIMIT); - const includeLivestreams = !tagsQuery; - const [liveSection, setLiveSection] = useState(includeLivestreams ? liveSectionStore : SECTION.HIDDEN); - const livestreamUris = includeLivestreams && getLivestreamUris(activeLivestreams, channelIds); - const liveTilesOverLimit = livestreamUris && livestreamUris.length > initialLiveTileLimit; - const useDualList = liveSection === SECTION.LESS && liveTilesOverLimit; function getMeta() { - return ( - <> - {!dynamicRouteProps ? ( - - }}>Results boosted by %lbc% - - ) : ( - tag && - !isMobile && ( -