From 09b689ba1ca278dcf71123fd425efb0e57fd5456 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 16 Feb 2021 16:09:20 -0500 Subject: [PATCH] add channel staked amount indicator on channel thumbnails --- package.json | 1 + ui/component/channelStakedIndicator/index.js | 9 ++ ui/component/channelStakedIndicator/view.jsx | 110 ++++++++++++++++++ ui/component/channelThumbnail/view.jsx | 12 +- ui/component/claimPreview/view.jsx | 30 ++--- ui/component/claimPreviewSubtitle/view.jsx | 5 +- ui/component/claimPreviewTile/view.jsx | 32 ++--- ui/component/claimRepostAuthor/view.jsx | 2 +- ui/component/comment/view.jsx | 11 +- ui/component/common/icon-custom.jsx | 82 ++++++++++++- ui/component/common/lbc-symbol.jsx | 5 +- ui/component/common/tooltip.jsx | 8 +- .../fileThumbnail/FreezeframeLite/styles.scss | 1 - .../fileThumbnail/FreezeframeWrapper.jsx | 2 +- ui/component/router/view.jsx | 10 +- ui/component/uriIndicator/view.jsx | 3 +- ui/component/wunderbarSuggestion/view.jsx | 2 +- ui/constants/icons.js | 5 + ui/page/channel/view.jsx | 12 +- ui/redux/selectors/search.js | 15 ++- ui/scss/all.scss | 2 +- ui/scss/component/_channel.scss | 59 +++++++++- ui/scss/component/_claim-list.scss | 22 ++-- ui/scss/component/_comments.scss | 1 + ui/scss/component/_header.scss | 5 +- ui/scss/component/_media.scss | 6 +- ui/scss/component/_subscriptions.scss | 4 - ui/scss/component/_tooltip.scss | 8 ++ ui/scss/component/_wunderbar.scss | 3 + ui/scss/themes/dark.scss | 1 + ui/scss/themes/light.scss | 2 + yarn.lock | 20 ++++ 32 files changed, 404 insertions(+), 86 deletions(-) create mode 100644 ui/component/channelStakedIndicator/index.js create mode 100644 ui/component/channelStakedIndicator/view.jsx delete mode 100644 ui/scss/component/_subscriptions.scss create mode 100644 ui/scss/component/_tooltip.scss diff --git a/package.json b/package.json index 415108246..acd3d2e3f 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@reach/menu-button": "0.7.4", "@reach/rect": "^0.13.0", "@reach/tabs": "^0.1.5", + "@reach/tooltip": "^0.12.1", "@reach/utils": "^0.12.1", "@sentry/browser": "^5.12.1", "@sentry/webpack-plugin": "^1.10.0", diff --git a/ui/component/channelStakedIndicator/index.js b/ui/component/channelStakedIndicator/index.js new file mode 100644 index 000000000..db473b24a --- /dev/null +++ b/ui/component/channelStakedIndicator/index.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { makeSelectClaimForUri } from 'lbry-redux'; +import ChannelStakedIndicator from './view'; + +const select = (state, props) => ({ + channelClaim: makeSelectClaimForUri(props.uri)(state), +}); + +export default connect(select)(ChannelStakedIndicator); diff --git a/ui/component/channelStakedIndicator/view.jsx b/ui/component/channelStakedIndicator/view.jsx new file mode 100644 index 000000000..b6715ed75 --- /dev/null +++ b/ui/component/channelStakedIndicator/view.jsx @@ -0,0 +1,110 @@ +// @flow +import * as ICONS from 'constants/icons'; +import React from 'react'; +import classnames from 'classnames'; +import Icon from 'component/common/icon'; +import LbcSymbol from 'component/common/lbc-symbol'; +import Tooltip from 'component/common/tooltip'; +import CreditAmount from 'component/common/credit-amount'; + +type Props = { + channelClaim: ChannelClaim, + large?: boolean, +}; + +function getChannelLevel(amount: number): number { + let level = 1; + + switch (true) { + case amount >= 10 && amount < 1000: + level = 2; + break; + case amount >= 1000 && amount < 10000: + level = 3; + break; + case amount >= 10000 && amount < 500000: + level = 4; + break; + case amount > 500000: + level = 5; + break; + } + + return level; +} + +function getChannelIcon(level: number): string { + const icons = { + '1': ICONS.CHANNEL_LEVEL_1, + '2': ICONS.CHANNEL_LEVEL_2, + '3': ICONS.CHANNEL_LEVEL_3, + '4': ICONS.CHANNEL_LEVEL_4, + '5': ICONS.CHANNEL_LEVEL_5, + }; + + return icons[level] || ICONS.CHANNEL_LEVEL_1; +} + +function ChannelStakedIndicator(props: Props) { + const { channelClaim, large = false } = props; + + if (!channelClaim || !channelClaim.meta) { + return null; + } + + const amount = parseFloat(channelClaim.amount) + parseFloat(channelClaim.meta.support_amount) || 0; + const isControlling = channelClaim && channelClaim.meta.is_controlling; + const level = getChannelLevel(amount); + const icon = getChannelIcon(level); + + return ( + +
+ +
+ +
+ {__('Level %current_level%', { current_level: level })} +
+ } size={14} /> +
+
+ + } + > +
+ +
+
+ ); +} + +type LevelIconProps = { + isControlling: boolean, + icon: string, + large?: boolean, +}; + +function LevelIcon(props: LevelIconProps) { + const { large, isControlling, icon } = props; + return ( + icon && ( + + ) + ); +} + +export default ChannelStakedIndicator; diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index 3790cff65..d3af5c508 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -4,6 +4,7 @@ import { parseURI } from 'lbry-redux'; import classnames from 'classnames'; import Gerbil from './gerbil.png'; import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper'; +import ChannelStakedIndicator from 'component/channelStakedIndicator'; type Props = { thumbnail: ?string, @@ -14,9 +15,10 @@ type Props = { small?: boolean, allowGifs?: boolean, claim: ?ChannelClaim, - doResolveUri: string => void, + doResolveUri: (string) => void, isResolving: boolean, showDelayedMessage?: boolean, + hideStakedIndicator?: boolean, }; function ChannelThumbnail(props: Props) { @@ -32,6 +34,7 @@ function ChannelThumbnail(props: Props) { doResolveUri, isResolving, showDelayedMessage = false, + hideStakedIndicator = false, } = props; const [thumbError, setThumbError] = React.useState(false); const shouldResolve = claim === undefined; @@ -57,7 +60,11 @@ function ChannelThumbnail(props: Props) { }, [doResolveUri, shouldResolve, uri]); if (channelThumbnail && channelThumbnail.endsWith('gif') && !allowGifs) { - return ; + return ( + + {!hideStakedIndicator && } + + ); } return ( @@ -90,6 +97,7 @@ function ChannelThumbnail(props: Props) { )} )} + {!hideStakedIndicator && } ); } diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 7a036f44c..d3794537b 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -36,9 +36,9 @@ type Props = { claimIsMine: boolean, pending?: boolean, reflectingProgress?: any, // fxme - resolveUri: string => void, + resolveUri: (string) => void, isResolvingUri: boolean, - history: { push: string => void }, + history: { push: (string) => void }, title: string, nsfw: boolean, placeholder: string, @@ -56,18 +56,18 @@ type Props = { channelIsBlocked: boolean, isSubscribed: boolean, actions: boolean | Node | string | number, - properties: boolean | Node | string | number | (Claim => Node), + properties: boolean | Node | string | number | ((Claim) => Node), empty?: Node, - onClick?: any => any, + onClick?: (any) => any, hideBlock?: boolean, streamingUrl: ?string, - getFile: string => void, - customShouldHide?: Claim => boolean, + getFile: (string) => void, + customShouldHide?: (Claim) => boolean, showUnresolvedClaim?: boolean, showNullPlaceholder?: boolean, includeSupportAction?: boolean, hideActions?: boolean, - renderActions?: Claim => ?Node, + renderActions?: (Claim) => ?Node, wrapperElement?: string, hideRepostLabel?: boolean, repostUrl?: string, @@ -144,7 +144,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { const navigateUrl = formatLbryUrlForWeb((claim && claim.canonical_url) || uri || '/'); const navLinkProps = { to: navigateUrl, - onClick: e => e.stopPropagation(), + onClick: (e) => e.stopPropagation(), }; // do not block abandoned and nsfw claims if showUserBlocked is passed @@ -156,7 +156,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { // This will be replaced once blocking is done at the wallet server level if (claim && !claimIsMine && !shouldHide && blackListedOutpoints) { shouldHide = blackListedOutpoints.some( - outpoint => + (outpoint) => (signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) || (outpoint.txid === claim.txid && outpoint.nout === claim.nout) ); @@ -165,19 +165,19 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { // or signing channel outpoint is in the filter list if (claim && !claimIsMine && !shouldHide && filteredOutpoints) { shouldHide = filteredOutpoints.some( - outpoint => + (outpoint) => (signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) || (outpoint.txid === claim.txid && outpoint.nout === claim.nout) ); } // block stream claims if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) { - shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url); + shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === signingChannel.permanent_url); } // block channel claims if we can't control for them in claim search // e.g. fetchRecommendedSubscriptions if (claim && isChannelUri && !shouldHide && !showUserBlocked && blockedChannelUris.length) { - shouldHide = blockedChannelUris.some(blockedUri => blockedUri === claim.permanent_url); + shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url); } if (!shouldHide && customShouldHide && claim) { @@ -220,7 +220,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { if (isValid && !isResolvingUri && shouldFetch && uri) { resolveUri(uri); } - }, [isValid, uri, isResolvingUri, shouldFetch]); + }, [isValid, uri, isResolvingUri, shouldFetch, resolveUri]); if (shouldHide && !showNullPlaceholder) { return null; @@ -319,6 +319,10 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { )} + + {type !== 'small' && !isChannelUri && signingChannel && ( + + )} {(pending || !!reflectingProgress) && } diff --git a/ui/component/claimPreviewSubtitle/view.jsx b/ui/component/claimPreviewSubtitle/view.jsx index 2fd2b18dd..f4d4c2015 100644 --- a/ui/component/claimPreviewSubtitle/view.jsx +++ b/ui/component/claimPreviewSubtitle/view.jsx @@ -3,6 +3,7 @@ import React from 'react'; import UriIndicator from 'component/uriIndicator'; import DateTime from 'component/dateTime'; import Button from 'component/button'; +import ChannelThumbnail from 'component/channelThumbnail'; import { parseURI } from 'lbry-redux'; type Props = { @@ -10,12 +11,13 @@ type Props = { claim: ?Claim, pending?: boolean, type: string, - beginPublish: string => void, + beginPublish: (string) => void, }; function ClaimPreviewSubtitle(props: Props) { const { pending, uri, claim, type, beginPublish } = props; const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; + const channelUri = claim && claim.signing_channel && claim.signing_channel.permanent_url; let isChannel; let name; @@ -27,6 +29,7 @@ function ClaimPreviewSubtitle(props: Props) {
{claim ? ( + {!isChannel && channelUri && type !== 'small' && } {' '} {!pending && claim && diff --git a/ui/component/claimPreviewTile/view.jsx b/ui/component/claimPreviewTile/view.jsx index 651e20333..9be6da5e3 100644 --- a/ui/component/claimPreviewTile/view.jsx +++ b/ui/component/claimPreviewTile/view.jsx @@ -18,9 +18,9 @@ import ClaimRepostAuthor from 'component/claimRepostAuthor'; type Props = { uri: string, claim: ?Claim, - resolveUri: string => void, + resolveUri: (string) => void, isResolvingUri: boolean, - history: { push: string => void }, + history: { push: (string) => void }, thumbnail: string, title: string, placeholder: boolean, @@ -33,7 +33,7 @@ type Props = { nout: number, }>, blockedChannelUris: Array, - getFile: string => void, + getFile: (string) => void, placeholder: boolean, streamingUrl: string, isMature: boolean, @@ -66,7 +66,7 @@ function ClaimPreviewTile(props: Props) { const navLinkProps = { to: navigateUrl, - onClick: e => e.stopPropagation(), + onClick: (e) => e.stopPropagation(), }; let isChannel; @@ -80,13 +80,10 @@ function ClaimPreviewTile(props: Props) { } } + let channelUri; const signingChannel = claim && claim.signing_channel; - let channelThumbnail; if (signingChannel) { - channelThumbnail = - // I should be able to just pass the the uri to but it wasn't working - // Come back to me - (signingChannel.value && signingChannel.value.thumbnail && signingChannel.value.thumbnail.url) || undefined; + channelUri = signingChannel.permanent_url; } function handleClick(e) { @@ -112,7 +109,7 @@ function ClaimPreviewTile(props: Props) { // This will be replaced once blocking is done at the wallet server level if (claim && !shouldHide && blackListedOutpoints) { shouldHide = blackListedOutpoints.some( - outpoint => + (outpoint) => (signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) || (outpoint.txid === claim.txid && outpoint.nout === claim.nout) ); @@ -121,7 +118,7 @@ function ClaimPreviewTile(props: Props) { // or signing channel outpoint is in the filter list if (claim && !shouldHide && filteredOutpoints) { shouldHide = filteredOutpoints.some( - outpoint => + (outpoint) => (signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) || (outpoint.txid === claim.txid && outpoint.nout === claim.nout) ); @@ -129,12 +126,12 @@ function ClaimPreviewTile(props: Props) { // block stream claims if (claim && !shouldHide && blockedChannelUris.length && signingChannel) { - shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url); + shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === signingChannel.permanent_url); } // block channel claims if we can't control for them in claim search // e.g. fetchRecommendedSubscriptions if (claim && isChannel && !shouldHide && blockedChannelUris.length) { - shouldHide = blockedChannelUris.some(blockedUri => blockedUri === claim.permanent_url); + shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url); } if (shouldHide) { @@ -179,7 +176,12 @@ function ClaimPreviewTile(props: Props) {

- + + {isChannel && ( +
+ +
+ )}

@@ -191,7 +193,7 @@ function ClaimPreviewTile(props: Props) { ) : ( - +
diff --git a/ui/component/claimRepostAuthor/view.jsx b/ui/component/claimRepostAuthor/view.jsx index 7634ac582..4525560b0 100644 --- a/ui/component/claimRepostAuthor/view.jsx +++ b/ui/component/claimRepostAuthor/view.jsx @@ -47,7 +47,7 @@ function ClaimRepostAuthor(props: Props) { return (
- + }}> %repost_channel_link% reposted diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index 61b042b8a..955039988 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -20,6 +20,7 @@ import CommentsReplies from 'component/commentsReplies'; import { useHistory } from 'react-router'; import CommentCreate from 'component/commentCreate'; import CommentMenuList from 'component/commentMenuList'; +import UriIndicator from 'component/uriIndicator'; type Props = { closeInlinePlayer: () => void, @@ -33,7 +34,7 @@ type Props = { claimIsMine: boolean, // if you control the claim which this comment was posted on commentIsMine: boolean, // if this comment was signed by an owned channel updateComment: (string, string) => void, - commentModBlock: string => void, + commentModBlock: (string) => void, linkedComment?: any, myChannels: ?Array, commentingEnabled: boolean, @@ -105,7 +106,7 @@ function Comment(props: Props) { setCharCount(editedMessage.length); // a user will try and press the escape key to cancel editing their comment - const handleEscape = event => { + const handleEscape = (event) => { if (event.keyCode === ESCAPE_KEY) { setEditing(false); } @@ -180,11 +181,7 @@ function Comment(props: Props) { {!author ? ( {__('Anonymous')} ) : ( -