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 { 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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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%', {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue