add channel staked amount indicator on channel thumbnails

This commit is contained in:
Sean Yesmunt 2021-02-16 16:09:20 -05:00
parent d26d76fc70
commit 09b689ba1c
32 changed files with 404 additions and 86 deletions

View file

@ -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",

View file

@ -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);

View file

@ -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 (
<Tooltip
label={
<div className="channel-staked__tooltip">
<div className="channel-staked__tooltip-icons">
<LevelIcon icon={icon} isControlling={isControlling} size={isControlling ? 14 : 10} />
</div>
<div className="channel-staked__tooltip-text">
<span>{__('Level %current_level%', { current_level: level })}</span>
<div className="channel-staked__amount">
<LbcSymbol postfix={<CreditAmount amount={amount} showLBC={false} />} size={14} />
</div>
</div>
</div>
}
>
<div
className={classnames('channel-staked__wrapper', {
'channel-staked__wrapper--large': large,
})}
>
<LevelIcon icon={icon} large={large} isControlling={isControlling} />
</div>
</Tooltip>
);
}
type LevelIconProps = {
isControlling: boolean,
icon: string,
large?: boolean,
};
function LevelIcon(props: LevelIconProps) {
const { large, isControlling, icon } = props;
return (
icon && (
<Icon
icon={icon}
size={large ? 36 : 14}
className={classnames('channel-staked__indicator', {
'channel-staked__indicator--large': large,
'channel-staked__indicator--controlling': isControlling,
})}
/>
)
);
}
export default ChannelStakedIndicator;

View file

@ -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 <FreezeframeWrapper src={channelThumbnail} className={classnames('channel-thumbnail', className)} />;
return (
<FreezeframeWrapper src={channelThumbnail} className={classnames('channel-thumbnail', className)}>
{!hideStakedIndicator && <ChannelStakedIndicator uri={uri} claim={claim} />}
</FreezeframeWrapper>
);
}
return (
@ -90,6 +97,7 @@ function ChannelThumbnail(props: Props) {
)}
</>
)}
{!hideStakedIndicator && <ChannelStakedIndicator uri={uri} claim={claim} />}
</div>
);
}

View file

@ -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<any, {}>((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<any, {}>((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<any, {}>((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<any, {}>((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<any, {}>((props: Props, ref: any) => {
<ClaimPreviewTitle uri={uri} />
</NavLink>
)}
{type !== 'small' && !isChannelUri && signingChannel && (
<ChannelThumbnail uri={signingChannel.permanent_url} />
)}
</div>
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}

View file

@ -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) {
<div className="media__subtitle">
{claim ? (
<React.Fragment>
{!isChannel && channelUri && type !== 'small' && <ChannelThumbnail uri={channelUri} />}
<UriIndicator uri={uri} link />{' '}
{!pending &&
claim &&

View file

@ -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<string>,
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 <ChannelThumbnail /> 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) {
</NavLink>
<NavLink {...navLinkProps}>
<h2 className="claim-tile__title">
<TruncatedText text={title || (claim && claim.name)} lines={2} />
<TruncatedText text={title || (claim && claim.name)} lines={isChannel ? 1 : 2} />
{isChannel && (
<div className="claim-tile__about">
<UriIndicator uri={uri} link />
</div>
)}
</h2>
</NavLink>
<div>
@ -191,7 +193,7 @@ function ClaimPreviewTile(props: Props) {
) : (
<React.Fragment>
<UriIndicator uri={uri} link hideAnonymous>
<ChannelThumbnail thumbnailPreview={channelThumbnail} />
<ChannelThumbnail uri={channelUri} />
</UriIndicator>
<div className="claim-tile__about">

View file

@ -47,7 +47,7 @@ function ClaimRepostAuthor(props: Props) {
return (
<div className="claim-preview__repost-author">
<Icon icon={ICONS.REPOST} size={10} />
<Icon icon={ICONS.REPOST} size={10} className="claim-preview__repost-icon" />
<I18nMessage tokens={{ repost_channel_link: <UriIndicator link uri={repostChannelUrl} /> }}>
%repost_channel_link% reposted
</I18nMessage>

View file

@ -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<ChannelClaim>,
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 ? (
<span className="comment__author">{__('Anonymous')}</span>
) : (
<Button
className="button--uri-indicator truncated-text comment__author"
navigate={authorUri}
label={author}
/>
<UriIndicator link uri={authorUri} />
)}
<Button
className="comment__time"

View file

@ -2,7 +2,6 @@
// A housing for all of our icons. Mostly taken from https://github.com/feathericons/react-feather
import * as ICONS from 'constants/icons';
import React, { forwardRef } from 'react';
// $FlowFixMe
import { v4 as uuid } from 'uuid';
type IconProps = {
@ -12,6 +11,7 @@ type IconProps = {
type CustomProps = {
size?: number,
className?: string,
};
// Returns a react component
@ -1020,4 +1020,84 @@ export const icons = {
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</g>
),
[ICONS.CHANNEL_LEVEL_1]: (props: CustomProps) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 26 24"
width={props.size || '18'}
height={props.size || '18'}
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 4.32001L14.025 8.02501C14.4776 8.85522 15.1598 9.53744 15.99 9.99001L19.68 12L15.975 14.025C15.1448 14.4776 14.4626 15.1598 14.01 15.99L12 19.68L9.97501 15.975C9.52419 15.1524 8.84761 14.4758 8.02501 14.025L4.32001 12L8.02501 9.97501C8.84761 9.52419 9.52419 8.84761 9.97501 8.02501L12 4.32001Z" />
</svg>
),
[ICONS.CHANNEL_LEVEL_2]: (props: CustomProps) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 26 24"
width={props.size || '18'}
height={props.size || '18'}
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M15.705 7.31999L17.625 10.845C18.0591 11.6316 18.7099 12.2772 19.5 12.705L23.025 14.625L19.5 16.5C18.7154 16.9301 18.0701 17.5754 17.64 18.36L15.72 21.885L13.785 18.435C13.3625 17.6207 12.7106 16.9479 11.91 16.5L8.38499 14.64L11.91 12.705C12.7001 12.2772 13.3508 11.6316 13.785 10.845L15.705 7.31999ZM4.37999 4.31999C4.10558 4.81954 3.69454 5.23058 3.19499 5.50499L0.974991 6.67499L3.19499 7.88999C3.68451 8.14404 4.09453 8.5281 4.37999 8.99999L5.59499 11.22L6.80999 8.99999C7.0844 8.50045 7.49544 8.08941 7.99499 7.81499L10.215 6.59999L7.99499 5.44499C7.49544 5.17058 7.0844 4.75954 6.80999 4.25999L5.59499 2.05499L4.37999 4.31999Z" />
</svg>
),
[ICONS.CHANNEL_LEVEL_3]: (props: CustomProps) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={props.size || '18'}
height={props.size || '18'}
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 7.95L13.92 11.475C14.3519 12.2672 15.0028 12.9181 15.795 13.35L19.32 15.27L15.795 17.19C15.0104 17.6201 14.3651 18.2654 13.935 19.05L12 22.59L10.08 19.065C9.64989 18.2804 9.00456 17.6351 8.22 17.205L4.68 15.27L8.205 13.35C8.99718 12.9181 9.64814 12.2672 10.08 11.475L12 7.95ZM3.57 3.705C3.29213 4.2198 2.8698 4.64213 2.355 4.92L0 6.18L2.34 7.5C2.8548 7.77788 3.27713 8.2002 3.555 8.715L4.815 10.95L6.075 8.655C6.36202 8.16237 6.78348 7.76173 7.29 7.5L9.585 6.24L7.29 4.92C6.7752 4.64213 6.35287 4.2198 6.075 3.705L4.815 1.41L3.57 3.705ZM17.625 3.825C17.3305 4.36309 16.8881 4.80555 16.35 5.1L13.935 6.42L16.35 7.74C16.8881 8.03446 17.3305 8.47692 17.625 9.015L18.945 11.43L20.265 9C20.5595 8.46191 21.0019 8.01946 21.54 7.725L24 6.42L21.54 5.1C21.0019 4.80555 20.5595 4.36309 20.265 3.825L18.945 1.41L17.625 3.825Z" />
</svg>
),
[ICONS.CHANNEL_LEVEL_4]: (props: CustomProps) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={props.size || '18'}
height={props.size || '18'}
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M4.5 6.375L5.64 8.46C5.89614 8.92861 6.28139 9.31386 6.75 9.57L8.835 10.71L6.75 11.85C6.28139 12.1061 5.89614 12.4914 5.64 12.96L4.5 15L3.285 12.96C3.02886 12.4914 2.64361 12.1061 2.175 11.85L0.0899963 10.71L2.175 9.57C2.64361 9.31386 3.02886 8.92861 3.285 8.46L4.5 6.375ZM17.67 10.125C17.3718 10.6692 16.9242 11.1168 16.38 11.415L13.875 12.75L16.32 14.085C16.8642 14.3832 17.3118 14.8308 17.61 15.375L18.945 17.82L20.28 15.375C20.5782 14.8308 21.0258 14.3832 21.57 14.085L24 12.75L21.555 11.415C21.0108 11.1168 20.5632 10.6692 20.265 10.125L18.93 7.68L17.67 10.125ZM8.49 21C8.93984 21.2454 9.30955 21.6152 9.555 22.065L10.65 24L11.745 21.99C11.9975 21.5699 12.3608 21.2274 12.795 21L14.805 19.905L12.795 18.75C12.3451 18.5046 11.9754 18.1348 11.73 17.685L10.65 15.69L9.555 17.7C9.30955 18.1498 8.93984 18.5196 8.49 18.765L6.48 19.86L8.49 21ZM12.9 4.68C13.2986 4.8985 13.6265 5.22638 13.845 5.625L14.82 7.41L15.795 5.625C16.0135 5.22638 16.3414 4.8985 16.74 4.68L18.525 3.705L16.74 2.73C16.3414 2.5115 16.0135 2.18362 15.795 1.785L14.82 0L13.845 1.785C13.6265 2.18362 13.2986 2.5115 12.9 2.73L11.115 3.705L12.9 4.68Z" />
</svg>
),
[ICONS.CHANNEL_LEVEL_5]: (props: CustomProps) => (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={props.size || '18'}
height={props.size || '18'}
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 4.89L13.875 8.31C14.291 9.07838 14.9216 9.70903 15.69 10.125L19.11 12L15.69 13.875C14.9216 14.291 14.291 14.9216 13.875 15.69L12 19.11L10.125 15.69C9.70903 14.9216 9.07838 14.291 8.31 13.875L4.89 12L8.31 10.125C9.07838 9.70903 9.70903 9.07838 10.125 8.31L12 4.89ZM3 1.89C2.76692 2.36024 2.3952 2.74767 1.935 3L0 4.005L1.935 5.055C2.38429 5.28744 2.75483 5.64753 3 6.09L4.05 8.025L5.1 6.09C5.33543 5.65943 5.68943 5.30543 6.12 5.07L8.055 4.02L6.09 3C5.65943 2.76457 5.30543 2.41057 5.07 1.98L4.005 0L3 1.89ZM18.975 1.89C18.7396 2.32057 18.3856 2.67457 17.955 2.91L16.02 3.96L17.955 5.01C18.3856 5.24543 18.7396 5.59943 18.975 6.03L20.025 7.965L21 6.09C21.2354 5.65943 21.5894 5.30543 22.02 5.07L24 4.005L22.065 3C21.6114 2.75957 21.2404 2.38858 21 1.935L19.995 0L18.975 1.89ZM3 17.91C2.76457 18.3406 2.41057 18.6946 1.98 18.93L0 19.995L1.935 21.045C2.36557 21.2804 2.71957 21.6344 2.955 22.065L4.005 24L5.055 22.065C5.28744 21.6157 5.64753 21.2452 6.09 21L8.025 19.95L6.09 18.9C5.65943 18.6646 5.30543 18.3106 5.07 17.88L4.02 15.945L3 17.91ZM18.975 17.91C18.7396 18.3406 18.3856 18.6946 17.955 18.93L16.02 19.98L17.955 21.03C18.3856 21.2654 18.7396 21.6194 18.975 22.05L20.025 23.985L21.075 22.05C21.3104 21.6194 21.6644 21.2654 22.095 21.03L24.03 19.98L22.095 18.93C21.6644 18.6946 21.3104 18.3406 21.075 17.91L20.025 15.975L18.975 17.91Z" />
</svg>
),
};

View file

@ -1,4 +1,5 @@
// @flow
import type { Node } from 'react';
import * as ICONS from 'constants/icons';
import React from 'react';
import classnames from 'classnames';
@ -8,8 +9,8 @@ type Props = {
withText?: boolean,
isTitle?: boolean,
size?: number,
prefix?: string | number,
postfix?: string | number,
prefix?: string | number | Node,
postfix?: string | number | Node,
};
const LbcSymbol = (props: Props) => {

View file

@ -1,6 +1,8 @@
// @flow
import type { Node } from 'react';
import React from 'react';
import ReachTooltip from '@reach/tooltip';
import '@reach/tooltip/styles.css';
type Props = {
label: string | Node,
@ -10,11 +12,7 @@ type Props = {
function Tooltip(props: Props) {
const { children, label } = props;
if (typeof label !== 'string') {
return children;
}
return <span title={label}>{children}</span>;
return <ReachTooltip label={label}>{children}</ReachTooltip>;
}
export default Tooltip;

View file

@ -23,7 +23,6 @@ $transition-duration: 300ms;
top: 0;
left: 0;
pointer-events: none;
z-index: $base-zindex + 1;
vertical-align: top;
opacity: 0;

View file

@ -3,7 +3,7 @@ import classnames from 'classnames';
import PropTypes from 'prop-types';
import Freezeframe from './FreezeframeLite';
const FreezeframeWrapper = props => {
const FreezeframeWrapper = (props) => {
const imgRef = React.useRef();
const freezeframe = React.useRef();
// eslint-disable-next-line

View file

@ -70,17 +70,17 @@ type Props = {
index: number,
length: number,
location: { pathname: string },
push: string => void,
push: (string) => void,
state: {},
replaceState: ({}, string, string) => void,
listen: any => () => void,
listen: (any) => () => void,
},
uri: string,
title: string,
welcomeVersion: number,
hasNavigated: boolean,
setHasNavigated: () => void,
setReferrer: string => void,
setReferrer: (string) => void,
hasUnclaimedRefereeReward: boolean,
homepageData: any,
};
@ -95,7 +95,7 @@ function PrivateRoute(props: PrivateRouteProps) {
return (
<Route
{...rest}
render={props =>
render={(props) =>
isAuthenticated || !IS_WEB ? (
<Component {...props} />
) : (
@ -220,7 +220,7 @@ function AppRouter(props: Props) {
<Route
key={dynamicRouteProps.route}
path={dynamicRouteProps.route}
component={routerProps => <DiscoverPage {...routerProps} dynamicRouteProps={dynamicRouteProps} />}
component={(routerProps) => <DiscoverPage {...routerProps} dynamicRouteProps={dynamicRouteProps} />}
/>
))}

View file

@ -9,11 +9,10 @@ type Props = {
channelUri: ?string,
link: ?boolean,
claim: ?Claim,
hideAnonymous: boolean,
// Lint thinks we aren't using these, even though we are.
// Possibly because the resolve function is an arrow function that is passed in props?
resolveUri: string => void,
resolveUri: (string) => void,
uri: string,
// to allow for other elements to be nested within the UriIndicator
children: ?Node,

View file

@ -28,7 +28,7 @@ export default function WunderbarSuggestion(props: Props) {
>
{isChannel ? <ChannelThumbnail uri={uri} /> : <FileThumbnail uri={uri} />}
<span className="wunderbar__suggestion-label">
<div>{claim.value.title}</div>
<div className="wunderbar__suggestion-title">{claim.value.title}</div>
<div className="wunderbar__suggestion-name">
{isChannel ? claim.name : (claim.signing_channel && claim.signing_channel.name) || __('Anonymous')}
</div>

View file

@ -131,3 +131,8 @@ export const BEST = 'Best';
export const CREATOR_LIKE = 'CreatorLike';
export const CHEF = 'Chef';
export const ANONYMOUS = 'Anonymous';
export const CHANNEL_LEVEL_1 = 'ChannelLevel1';
export const CHANNEL_LEVEL_2 = 'ChannelLevel2';
export const CHANNEL_LEVEL_3 = 'ChannelLevel3';
export const CHANNEL_LEVEL_4 = 'ChannelLevel4';
export const CHANNEL_LEVEL_5 = 'ChannelLevel5';

View file

@ -21,6 +21,7 @@ import ChannelEdit from 'component/channelEdit';
import classnames from 'classnames';
import HelpLink from 'component/common/help-link';
import ClaimSupportButton from 'component/claimSupportButton';
import ChannelStakedIndicator from 'component/channelStakedIndicator';
export const PAGE_VIEW_QUERY = `view`;
const ABOUT_PAGE = `about`;
@ -42,7 +43,7 @@ type Props = {
txid: string,
nout: number,
}>,
fetchSubCount: string => void,
fetchSubCount: (string) => void,
subCount: number,
pending: boolean,
youtubeChannels: ?Array<{ channel_claim_id: string, sync_status: string, transfer_state: string }>,
@ -93,7 +94,7 @@ function ChannelPage(props: Props) {
if (claim && blackListedOutpoints) {
channelIsBlackListed = blackListedOutpoints.some(
outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout
(outpoint) => outpoint.txid === claim.txid && outpoint.nout === claim.nout
);
}
@ -171,8 +172,12 @@ function ChannelPage(props: Props) {
uri={uri}
obscure={channelIsBlocked}
allowGifs
hideStakedIndicator
/>
<h1 className="channel__title">{title || '@' + channelName}</h1>
<h1 className="channel__title">
{title || '@' + channelName}
<ChannelStakedIndicator uri={uri} large />
</h1>
<div className="channel__meta">
<span>
{formattedSubCount} {subCount !== 1 ? __('Followers') : __('Follower')}
@ -198,6 +203,7 @@ function ChannelPage(props: Props) {
</div>
<div className="channel-cover__gradient" />
</header>
<Tabs onChange={onTabChange} index={tabIndex}>
<TabList className="tabs__list--channel-page">
<Tab disabled={editing}>{__('Content')}</Tab>

View file

@ -17,22 +17,25 @@ type State = { search: SearchState };
export const selectState = (state: State): SearchState => state.search;
export const selectSearchValue: (state: State) => string = createSelector(selectState, state => state.searchQuery);
export const selectSearchValue: (state: State) => string = createSelector(selectState, (state) => state.searchQuery);
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(selectState, state => state.options);
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(
selectState,
(state) => state.options
);
export const selectIsSearching: (state: State) => boolean = createSelector(selectState, state => state.searching);
export const selectIsSearching: (state: State) => boolean = createSelector(selectState, (state) => state.searching);
export const selectSearchUrisByQuery: (state: State) => { [string]: Array<string> } = createSelector(
selectState,
state => state.urisByQuery
(state) => state.urisByQuery
);
export const makeSelectSearchUris = (query: string): ((state: State) => Array<string>) =>
// replace statement below is kind of ugly, and repeated in doSearch action
createSelector(
selectSearchUrisByQuery,
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
(byQuery) => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
);
// Creates a query string based on the state in the search reducer
@ -82,7 +85,7 @@ export const makeSelectRecommendedContentForUri = (uri: string) =>
let searchUris = searchUrisByQuery[searchQuery];
if (searchUris) {
searchUris = searchUris.filter(searchUri => searchUri !== currentUri);
searchUris = searchUris.filter((searchUri) => searchUri !== currentUri);
recommendedContent = searchUris;
}
}

View file

@ -50,10 +50,10 @@
@import 'component/spinner';
@import 'component/splash';
@import 'component/status-bar';
@import 'component/subscriptions';
@import 'component/syntax-highlighter';
@import 'component/table';
@import 'component/tabs';
@import 'component/tooltip';
@import 'component/txo-list';
@import 'component/tags';
@import 'component/wunderbar';

View file

@ -61,6 +61,7 @@ $metadata-z-index: 1;
.channel-thumbnail {
display: flex;
position: relative;
height: 5rem;
width: 5rem;
background-size: cover;
@ -166,7 +167,7 @@ $metadata-z-index: 1;
// Quick hack to get this to work
// We should have a generic style for "header with button next to it"
.button {
margin-left: var(--spacing-m);
margin-left: var(--spacing-xs);
}
@media (max-width: $breakpoint-small) {
@ -351,3 +352,59 @@ $metadata-z-index: 1;
margin-bottom: var(--spacing-l);
}
}
.channel-staked__tooltip {
display: flex;
align-items: center;
}
.channel-staked__tooltip-text {
margin-left: var(--spacing-xs);
font-size: var(--font-xsmall);
.icon--LBC {
opacity: 0.7;
margin-top: -2px;
}
}
.channel-staked__wrapper {
display: flex;
position: absolute;
bottom: -0.9rem;
left: -0.9rem;
padding: 0.25rem;
background-color: var(--color-card-background);
border-radius: 50%;
}
.channel-staked__wrapper--large {
position: relative;
background-color: transparent;
display: inline-block;
bottom: auto;
left: auto;
top: var(--spacing-xs);
margin-left: var(--spacing-s);
padding: 0;
}
.channel-staked__indicator {
margin-left: 2px;
z-index: 3;
fill: var(--color-gray-3);
}
.channel-staked__indicator--controlling {
fill: #00eaff;
}
.channel-staked__amount {
display: inline-block;
font-size: var(--font-xxsmall);
margin-left: var(--spacing-s);
.credit-amount {
color: var(--color-gray-3);
}
}

View file

@ -169,6 +169,11 @@
.channel-thumbnail {
@include handleChannelGif(4rem);
}
.media__subtitle {
display: flex;
flex-direction: column;
}
}
.claim-preview--pending {
@ -242,6 +247,11 @@
.claim-preview-info {
align-items: flex-start;
.channel-thumbnail {
@include handleChannelGif(1.4rem);
margin-right: 0;
}
}
.claim-preview-info,
@ -425,10 +435,6 @@
&:nth-child(3n + 1) {
margin-left: 0;
}
.channel-thumbnail {
display: none;
}
}
@media (max-width: $breakpoint-small) {
@ -526,11 +532,11 @@
transform: translateY(calc(var(--spacing-xs) * -1));
font-size: var(--font-xsmall);
color: var(--color-text-subtitle);
}
.icon {
.claim-preview__repost-icon {
margin-right: var(--spacing-xxs);
margin-bottom: -1px; // Offset it slightly because it doesn't look aligned next to all lowercase text + the @ from a channel
}
}
.claim-tile__repost-author {

View file

@ -204,6 +204,7 @@ $thumbnailWidthSmall: 0rem;
opacity: 0.5;
white-space: nowrap;
height: 100%;
margin-left: var(--spacing-xs);
&:focus {
@include linkFocus;

View file

@ -46,10 +46,6 @@
display: flex;
justify-content: space-between;
align-items: center;
> .button:only-child {
margin-left: auto;
}
}
.header__buttons {
@ -133,6 +129,7 @@
.header__navigation-item--balance {
@extend .button--link;
margin-left: var(--spacing-m);
}
.header__navigation-item--forward {

View file

@ -40,8 +40,10 @@
color: var(--color-text-subtitle);
font-size: var(--font-small);
svg {
stroke: var(--color-text-subtitle);
.channel-thumbnail {
display: inline-block;
@include handleChannelGif(1rem);
margin-right: var(--spacing-xs);
}
}

View file

@ -1,4 +0,0 @@
.subscriptions__suggested {
animation: expand 0.2s;
position: relative;
}

View file

@ -0,0 +1,8 @@
[data-reach-tooltip] {
border-radius: var(--border-radius);
background-color: var(--color-tooltip-bg);
color: var(--color-tooltip-text);
border: none;
padding: var(--spacing-s);
overflow: hidden;
}

View file

@ -170,6 +170,9 @@
font-size: var(--font-small);
min-width: 0;
white-space: nowrap;
}
.wunderbar__suggestion-title {
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -59,6 +59,7 @@
--color-thumbnail-background: var(--color-gray-5);
--color-comment-threadline: #434b54;
--color-comment-threadline-hover: var(--color-gray-4);
--color-tooltip-bg: #2f3337;
// Text
--color-text: #d8d8d8;

View file

@ -28,6 +28,8 @@
--color-comment-threadline: var(--color-gray-1);
--color-comment-threadline-hover: var(--color-gray-4);
--color-thumbnail-background: var(--color-gray-1);
--color-tooltip-bg: #222;
--color-tooltip-text: #fafafa;
// Icons
--color-follow-bg: #ffd4da;

View file

@ -1493,6 +1493,19 @@
"@reach/utils" "^0.2.3"
warning "^4.0.2"
"@reach/tooltip@^0.12.1":
version "0.12.1"
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.12.1.tgz#76f8e2337aa7dda20928ec4bbca247d39b4326af"
integrity sha512-9qk7WcgAjN/2tFEHN3Oi1m7BrSsL+MBFTFaxTaNqQacXYIKO3jNVFzLxkDVgyFP82h6EMJ5CkAJjOMi4aNyicQ==
dependencies:
"@reach/auto-id" "0.12.1"
"@reach/portal" "0.12.1"
"@reach/rect" "0.12.1"
"@reach/utils" "0.12.1"
"@reach/visually-hidden" "0.12.0"
prop-types "^15.7.2"
tslib "^2.0.0"
"@reach/utils@0.12.1", "@reach/utils@^0.12.1":
version "0.12.1"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.12.1.tgz#02b9c15ba5cddf23e16f2d2ca77aef98a9702607"
@ -1519,6 +1532,13 @@
version "0.7.4"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.7.4.tgz#cdd0cff8c57445ce9c2b11204e6a62868f1093a1"
"@reach/visually-hidden@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.12.0.tgz#c6c39f0bf292ae97a480ba3ed32e8a6149e852d4"
integrity sha512-VXtyR36WOS0FHmFQ/BzfswwPyBXICl/YjmTPtx/I0Vy5C6djjhD9kDsE+h/FpHzP3ugTdqdqiPBVElIZTSwOSA==
dependencies:
tslib "^2.0.0"
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"