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:
parent
1a57b02f80
commit
e58ddbc809
6 changed files with 45 additions and 20 deletions
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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%', {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue