Show content view counts on channel pages

## Issue
3587 Show content view counts on channel pages

## Notes
Limited to just "channel pages" for now as specified in the ticket.

Can be enabled for all claim previews, as long as there's an efficient spot to run the batch fetching. Either make `fetchViewCount` prop default to true, or add the parameter in places that need it.
This commit is contained in:
infinite-persistence 2021-09-09 15:02:31 +08:00
parent 89a0d5e597
commit 093c427b83
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
12 changed files with 125 additions and 16 deletions

View file

@ -155,7 +155,7 @@
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#49b9db5aae8db84ec21231444bf8345d450812b2",
"lbryinc": "lbryio/lbryinc#8f9a58bfc8312a65614fd7327661cdcc502c4e59",
"lbryinc": "lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",
"lodash-es": "^4.17.14",

View file

@ -146,6 +146,7 @@ function ChannelContent(props: Props) {
defaultFreshness={CS.FRESH_ALL}
showHiddenByUser={viewHiddenChannels}
forceShowReposts
fetchViewCount
hideFilters={!showFilters}
hideAdvancedFilter={!showFilters}
tileLayout={tileLayout}

View file

@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import {
doClaimSearch,
selectClaimsByUri,
selectClaimSearchByQuery,
selectClaimSearchByQueryLastPageReached,
selectFetchingClaimSearch,
@ -12,11 +13,13 @@ import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting, selectShowMatureContent, selectLanguage } from 'redux/selectors/settings';
import { selectModerationBlockList } from 'redux/selectors/comments';
import ClaimListDiscover from './view';
import { doFetchViewCount } from 'lbryinc';
const select = (state) => ({
followedTags: selectFollowedTags(state),
claimSearchByQuery: selectClaimSearchByQuery(state),
claimSearchByQueryLastPageReached: selectClaimSearchByQueryLastPageReached(state),
claimsByUri: selectClaimsByUri(state),
loading: selectFetchingClaimSearch(state),
showNsfw: selectShowMatureContent(state),
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
@ -29,6 +32,7 @@ const select = (state) => ({
const perform = {
doClaimSearch,
doToggleTagFollowDesktop,
doFetchViewCount,
};
export default connect(select, perform)(ClaimListDiscover);

View file

@ -28,6 +28,7 @@ type Props = {
meta?: Node,
showNsfw: boolean,
hideReposts: boolean,
fetchViewCount?: boolean,
history: { action: string, push: (string) => void, replace: (string) => void },
location: { search: string, pathname: string },
claimSearchByQuery: {
@ -78,6 +79,8 @@ type Props = {
showNoSourceClaims?: boolean,
isChannel?: boolean,
empty?: string,
claimsByUri: { [string]: any },
doFetchViewCount: (claimIdCsv: string) => void,
};
function ClaimListDiscover(props: Props) {
@ -94,6 +97,7 @@ function ClaimListDiscover(props: Props) {
channelIds,
showNsfw,
hideReposts,
fetchViewCount,
history,
location,
mutedUris,
@ -138,6 +142,8 @@ function ClaimListDiscover(props: Props) {
isChannel = false,
showNoSourceClaims,
empty,
claimsByUri,
doFetchViewCount,
} = props;
const didNavigateForward = history.action === 'PUSH';
const { search } = location;
@ -518,6 +524,16 @@ function ClaimListDiscover(props: Props) {
}
}
function fetchViewCountForUris(uris) {
const claimIds = [];
uris.forEach((uri) => {
if (claimsByUri[uri]) {
claimIds.push(claimsByUri[uri].claim_id);
}
});
doFetchViewCount(claimIds.join(','));
}
// **************************************************************************
// **************************************************************************
@ -547,6 +563,12 @@ function ClaimListDiscover(props: Props) {
}
}, [uris, claimSearchResult, finalUris, setFinalUris, liveLivestreamsFirst, livestreamSearchResult]);
React.useEffect(() => {
if (fetchViewCount) {
fetchViewCountForUris(finalUris);
}
}, [finalUris]); // eslint-disable-line react-hooks/exhaustive-deps
const headerToUse = header || (
<ClaimListHeader
channelIds={channelIds}

View file

@ -4,6 +4,7 @@ import React from 'react';
import UriIndicator from 'component/uriIndicator';
import DateTime from 'component/dateTime';
import Button from 'component/button';
import FileViewCountInline from 'component/fileViewCountInline';
import { parseURI } from 'lbry-redux';
type Props = {
@ -37,7 +38,14 @@ function ClaimPreviewSubtitle(props: Props) {
`${claimsInChannel} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
{!isChannel &&
(isLivestream && ENABLE_NO_SOURCE_CLAIMS ? __('Livestream') : <DateTime timeAgo uri={uri} />)}
(isLivestream && ENABLE_NO_SOURCE_CLAIMS ? (
__('Livestream')
) : (
<>
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
<DateTime timeAgo uri={uri} />
</>
))}
</>
)}
</React.Fragment>

View file

@ -7,6 +7,7 @@ import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text';
import DateTime from 'component/dateTime';
import ChannelThumbnail from 'component/channelThumbnail';
import FileViewCountInline from 'component/fileViewCountInline';
import SubscribeButton from 'component/subscribeButton';
import useGetThumbnail from 'effects/use-get-thumbnail';
import { formatLbryUrlForWeb } from 'util/url';
@ -210,15 +211,11 @@ function ClaimPreviewTile(props: Props) {
{!isChannel && (
<React.Fragment>
<div className="claim-preview__hover-actions">
{isPlayable && (
<FileWatchLaterLink focusable={false} uri={uri} />
)}
{isPlayable && <FileWatchLaterLink focusable={false} uri={uri} />}
</div>
{/* @if TARGET='app' */}
<div className="claim-preview__hover-actions">
{isStream && (
<FileDownloadLink focusable={false} uri={canonicalUrl} hideOpenButton />
)}
{isStream && <FileDownloadLink focusable={false} uri={canonicalUrl} hideOpenButton />}
</div>
{/* @endif */}
@ -226,7 +223,6 @@ function ClaimPreviewTile(props: Props) {
<PreviewOverlayProperties uri={uri} properties={liveProperty || properties} />
</div>
</React.Fragment>
)}
{isCollection && (
<React.Fragment>
@ -264,7 +260,10 @@ function ClaimPreviewTile(props: Props) {
<div className="claim-tile__about">
<UriIndicator uri={uri} link />
<DateTime timeAgo uri={uri} />
<div className="claim-tile__about--counts">
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
<DateTime timeAgo uri={uri} />
</div>
</div>
</React.Fragment>
)}

View file

@ -6,6 +6,7 @@ import {
SETTINGS,
selectClaimsByUri,
} from 'lbry-redux';
import { doFetchViewCount } from 'lbryinc';
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import { selectModerationBlockList } from 'redux/selectors/comments';
@ -25,6 +26,7 @@ const select = (state) => ({
const perform = {
doClaimSearch,
doToggleTagFollowDesktop,
doFetchViewCount,
};
export default connect(select, perform)(ClaimListDiscover);

View file

@ -92,6 +92,7 @@ type Props = {
livestreamMap?: { [string]: any },
showNoSourceClaims?: boolean,
renderProperties?: (Claim) => ?Node,
fetchViewCount?: boolean,
// claim search options are below
tags: Array<string>,
claimIds?: Array<string>,
@ -117,6 +118,7 @@ type Props = {
blockedUris: Array<string>,
// --- perform ---
doClaimSearch: ({}) => void,
doFetchViewCount: (claimIdCsv: string) => void,
};
function ClaimTilesDiscover(props: Props) {
@ -126,6 +128,7 @@ function ClaimTilesDiscover(props: Props) {
claimsByUri,
showNsfw,
hideReposts,
fetchViewCount,
// Below are options to pass that are forwarded to claim_search
tags,
channelIds,
@ -150,6 +153,7 @@ function ClaimTilesDiscover(props: Props) {
pinUrls,
prefixUris,
showNoSourceClaims,
doFetchViewCount,
} = props;
const { location } = useHistory();
@ -294,6 +298,16 @@ function ClaimTilesDiscover(props: Props) {
return undefined;
}
function fetchViewCountForUris(uris) {
const claimIds = [];
uris.forEach((uri) => {
if (claimsByUri[uri]) {
claimIds.push(claimsByUri[uri].claim_id);
}
});
doFetchViewCount(claimIds.join(','));
}
// **************************************************************************
// **************************************************************************
@ -310,9 +324,14 @@ function ClaimTilesDiscover(props: Props) {
React.useEffect(() => {
if (JSON.stringify(prevUris) !== JSON.stringify(uris) && !shouldPerformSearch) {
// Stash new results for next render cycle:
setPrevUris(uris);
// Fetch view count:
if (fetchViewCount) {
fetchViewCountForUris(uris);
}
}
}, [shouldPerformSearch, prevUris, uris]);
}, [shouldPerformSearch, prevUris, uris]); // eslint-disable-line react-hooks/exhaustive-deps
// **************************************************************************
// **************************************************************************

View file

@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'lbry-redux';
import { makeSelectViewCountForUri } from 'lbryinc';
import FileViewCountInline from './view';
const select = (state, props) => {
return {
claim: makeSelectClaimForUri(props.uri)(state),
viewCount: makeSelectViewCountForUri(props.uri)(state),
};
};
export default connect(select, null)(FileViewCountInline);

View file

@ -0,0 +1,30 @@
// @flow
import React from 'react';
type Props = {
uri: string,
isLivestream?: boolean,
// --- select ---
claim: ?StreamClaim,
viewCount: string,
};
export default function FileViewCountInline(props: Props) {
const { isLivestream, claim, viewCount } = props;
const formattedViewCount = Number(viewCount).toLocaleString();
if (!viewCount || (claim && claim.repost_url) || isLivestream) {
// (1) Currently, makeSelectViewCountForUri doesn't differentiate between
// unfetched view-count vs zero view-count. But since it's probably not
// ideal to highlight that a view has 0 count, let's just not show anything.
// (2) No idea how to get the repost src's claim ID from the repost claim,
// so hiding it for now.
return null;
}
return (
<span className="view_count">
{viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view')}
</span>
);
}

View file

@ -598,6 +598,18 @@
font-size: var(--font-body);
}
.claim-tile__about--counts {
display: flex;
flex-wrap: wrap;
}
.view_count {
&::after {
content: '';
margin: 0 var(--spacing-xxs);
}
}
.claim-preview__file-property-overlay {
position: absolute;
bottom: var(--spacing-xxs);
@ -681,14 +693,13 @@
// div that displays watch later button
.claim-preview__hover-actions {
// if the user is using a mouse
@media (pointer: fine){
@media (pointer: fine) {
display: none;
}
// if the user doesn't have a pointer (mouse etc) hide watch later button
@media (pointer: none), (pointer:coarse) {
@media (pointer: none), (pointer: coarse) {
display: none !important;
}

View file

@ -10148,9 +10148,9 @@ lbry-redux@lbryio/lbry-redux#49b9db5aae8db84ec21231444bf8345d450812b2:
reselect "^3.0.0"
uuid "^8.3.1"
lbryinc@lbryio/lbryinc#8f9a58bfc8312a65614fd7327661cdcc502c4e59:
lbryinc@lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/8f9a58bfc8312a65614fd7327661cdcc502c4e59"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/0b4e41ef90d6347819dd3453f2f9398a5c1b4f36"
dependencies:
reselect "^3.0.0"