View/Follower count: only use compact when > 10k (#664)

* Tooltip: add 'followCursor' and 'placement' option

When used on a `<span>` with short text but large empty area, the location of the tooltip was at the bottom-center of the area, which isn't ideal.

I think 'followCursor' should be the default, but making it optional for now to minimize testing.

Also added the 'placement' prop -- for the span case again, the mouse cursor is blocking the tooltip.

* View/Follower count: only use compact when > 10k

## Issue
Received complaints -- some people prefer to see full resolution.

## Changes
- As a compromise, we'll only apply the compact notation when the value is greater than 10k, with the exception of Tile View Count, where we'll always apply it due to space limitation.
- Also added Tooltip for Follower count.

## Fixes
- The string was always in 'en' locale in some instances, so it wasn't grouping up digits properly in Japanese (groups of 4), for example.
This commit is contained in:
infinite-persistence 2022-01-11 08:42:12 -08:00 committed by GitHub
parent 1a57b02f80
commit e58ddbc809
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 20 deletions

View file

@ -22,7 +22,7 @@ import { doCollectionEdit } from 'redux/actions/collections';
import { doFileGet } from 'redux/actions/file'; import { doFileGet } from 'redux/actions/file';
import { selectBanStateForUri } from 'lbryinc'; import { selectBanStateForUri } from 'lbryinc';
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream'; import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { isClaimNsfw } from 'util/claim'; import { isClaimNsfw } from 'util/claim';
@ -57,6 +57,7 @@ const select = (state, props) => {
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state), isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state), collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state), collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
lang: selectLanguage(state),
}; };
}; };

View file

@ -11,6 +11,7 @@ import { isChannelClaim } from 'util/claim';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
import { formatClaimPreviewTitle } from 'util/formatAriaLabel'; import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
import { toCompactNotation } from 'util/string'; import { toCompactNotation } from 'util/string';
import Tooltip from 'component/common/tooltip';
import FileThumbnail from 'component/fileThumbnail'; import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import PreviewOverlayProperties from 'component/previewOverlayProperties'; import PreviewOverlayProperties from 'component/previewOverlayProperties';
@ -88,6 +89,7 @@ type Props = {
indexInContainer?: number, // The index order of this component within 'containerId'. indexInContainer?: number, // The index order of this component within 'containerId'.
channelSubCount?: number, channelSubCount?: number,
swipeLayout: boolean, swipeLayout: boolean,
lang: string,
}; };
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -149,6 +151,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
indexInContainer, indexInContainer,
channelSubCount, channelSubCount,
swipeLayout = false, swipeLayout = false,
lang,
} = props; } = props;
const isCollection = claim && claim.value_type === 'collection'; const isCollection = claim && claim.value_type === 'collection';
@ -166,13 +169,13 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
if (channelSubCount === undefined) { if (channelSubCount === undefined) {
return <span />; return <span />;
} }
const formattedSubCount = toCompactNotation(channelSubCount, lang, 10000);
return ( return (
<span className="claim-preview__channel-sub-count"> <Tooltip title={channelSubCount} followCursor placement="top">
{channelSubCount === 1 <span className="claim-preview__channel-sub-count">
? __('1 Follower') {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })}
: __('%formattedSubCount% Followers', { formattedSubCount: toCompactNotation(channelSubCount) })} </span>
</span> </Tooltip>
); );
}, [channelSubCount]); }, [channelSubCount]);
const isValid = uri && isURIValid(uri, false); const isValid = uri && isURIValid(uri, false);

View file

@ -9,10 +9,20 @@ type Props = {
disableInteractive?: boolean, disableInteractive?: boolean,
enterDelay?: number, enterDelay?: number,
title?: string | Node, title?: string | Node,
followCursor?: boolean,
placement?: string, // https://mui.com/api/tooltip/
}; };
function Tooltip(props: Props) { function Tooltip(props: Props) {
const { arrow = true, children, disableInteractive = true, enterDelay = 300, title } = props; const {
arrow = true,
children,
disableInteractive = true,
enterDelay = 300,
title,
followCursor = false,
placement = 'bottom',
} = props;
return ( return (
<MUITooltip <MUITooltip
@ -21,6 +31,8 @@ function Tooltip(props: Props) {
enterDelay={enterDelay} enterDelay={enterDelay}
enterNextDelay={enterDelay} enterNextDelay={enterDelay}
title={title} title={title}
followCursor={followCursor}
placement={placement}
> >
{children} {children}
</MUITooltip> </MUITooltip>

View file

@ -1,6 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimIdForUri } from 'redux/selectors/claims'; import { selectClaimIdForUri } from 'redux/selectors/claims';
import { selectViewersForId } from 'redux/selectors/livestream'; import { selectViewersForId } from 'redux/selectors/livestream';
import { selectLanguage } from 'redux/selectors/settings';
import { doFetchViewCount, selectViewCountForUri } from 'lbryinc'; import { doFetchViewCount, selectViewCountForUri } from 'lbryinc';
import FileViewCount from './view'; import FileViewCount from './view';
@ -10,6 +11,7 @@ const select = (state, props) => {
claimId, claimId,
viewCount: selectViewCountForUri(state, props.uri), viewCount: selectViewCountForUri(state, props.uri),
activeViewers: props.livestream && claimId ? selectViewersForId(state, claimId) : undefined, activeViewers: props.livestream && claimId ? selectViewersForId(state, claimId) : undefined,
lang: selectLanguage(state),
}; };
}; };

View file

@ -14,12 +14,13 @@ type Props = {
uri: string, uri: string,
viewCount: string, viewCount: string,
activeViewers?: number, activeViewers?: number,
lang: string,
}; };
function FileViewCount(props: Props) { function FileViewCount(props: Props) {
const { claimId, fetchViewCount, viewCount, livestream, activeViewers, isLive = false } = props; const { claimId, fetchViewCount, viewCount, livestream, activeViewers, isLive = false, lang } = props;
const count = livestream ? activeViewers || 0 : viewCount; const count = livestream ? activeViewers || 0 : viewCount;
const countCompact = toCompactNotation(count); const countCompact = toCompactNotation(count, lang, 10000);
const countFullResolution = Number(count).toLocaleString(); const countFullResolution = Number(count).toLocaleString();
React.useEffect(() => { React.useEffect(() => {
@ -29,7 +30,7 @@ function FileViewCount(props: Props) {
}, [claimId]); // eslint-disable-line react-hooks/exhaustive-deps }, [claimId]); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
<Tooltip title={countFullResolution}> <Tooltip title={countFullResolution} followCursor placement="top">
<span className="media__subtitle--centered"> <span className="media__subtitle--centered">
{livestream && {livestream &&
__('%viewer_count% currently %viewer_state%', { __('%viewer_count% currently %viewer_state%', {

View file

@ -4,14 +4,20 @@ export function toCapitalCase(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }
export function toCompactNotation(number: string | number, lang: ?string) { export function toCompactNotation(number: string | number, lang: ?string, minThresholdToApply?: string | number) {
try { const locale = lang || 'en';
return Number(number).toLocaleString(lang || 'en', {
compactDisplay: 'short', if (minThresholdToApply && Number(number) >= Number(minThresholdToApply)) {
notation: 'compact', try {
}); return Number(number).toLocaleString(locale, {
} catch (err) { compactDisplay: 'short',
// Not all browsers support the addition options. notation: 'compact',
return Number(number).toLocaleString(); });
} catch (err) {
// Not all browsers support the additional options.
return Number(number).toLocaleString(locale);
}
} else {
return Number(number).toLocaleString(locale);
} }
} }