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

View file

@ -9,10 +9,20 @@ type Props = {
disableInteractive?: boolean,
enterDelay?: number,
title?: string | Node,
followCursor?: boolean,
placement?: string, // https://mui.com/api/tooltip/
};
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 (
<MUITooltip
@ -21,6 +31,8 @@ function Tooltip(props: Props) {
enterDelay={enterDelay}
enterNextDelay={enterDelay}
title={title}
followCursor={followCursor}
placement={placement}
>
{children}
</MUITooltip>

View file

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

View file

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

View file

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