Merge pull request #5167 from lbryio/repostFromSearchRebase
New repost flow
This commit is contained in:
commit
bb4cb337ec
33 changed files with 940 additions and 78 deletions
|
@ -139,7 +139,7 @@
|
||||||
"imagesloaded": "^4.1.4",
|
"imagesloaded": "^4.1.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#92a4263c908f6df5727c379a3b3348935891d806",
|
"lbry-redux": "lbryio/lbry-redux#664df6237d777d7cce31e58479bc98563ea98cb9",
|
||||||
"lbryinc": "lbryio/lbryinc#eee2cb730ecec95a1344a755035755b0d4dad5cf",
|
"lbryinc": "lbryio/lbryinc#eee2cb730ecec95a1344a755035755b0d4dad5cf",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
|
@ -1527,6 +1527,9 @@
|
||||||
"Explore": "Explore",
|
"Explore": "Explore",
|
||||||
"There is a bug... somewhere": "There is a bug... somewhere",
|
"There is a bug... somewhere": "There is a bug... somewhere",
|
||||||
"Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.": "Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.",
|
"Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.": "Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.",
|
||||||
|
"No Results": "No Results",
|
||||||
|
"Content preview": "Content preview",
|
||||||
|
"Repost url": "Repost url",
|
||||||
"Close sidebar - hide channels you are following": "Close sidebar - hide channels you are following",
|
"Close sidebar - hide channels you are following": "Close sidebar - hide channels you are following",
|
||||||
"Expand sidebar - view channels you are following.": "Expand sidebar - view channels you are following.",
|
"Expand sidebar - view channels you are following.": "Expand sidebar - view channels you are following.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
|
|
|
@ -14,7 +14,7 @@ function ClaimEffectiveAmount(props: Props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CreditAmount amount={Number(claim.meta.effective_amount)} />;
|
return <CreditAmount amount={Number(claim.meta.effective_amount || claim.amount)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClaimEffectiveAmount;
|
export default ClaimEffectiveAmount;
|
||||||
|
|
30
ui/component/claimPreview/claim-preview-loading.jsx
Normal file
30
ui/component/claimPreview/claim-preview-loading.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewLoading(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
||||||
|
<div className="placeholder media__thumb" />
|
||||||
|
<div className="placeholder__wrapper">
|
||||||
|
<div className="placeholder claim-preview__title" />
|
||||||
|
<div className="placeholder media__subtitle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewLoading;
|
32
ui/component/claimPreview/claim-preview-no-content.jsx
Normal file
32
ui/component/claimPreview/claim-preview-no-content.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewNoContent(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classnames('claim-preview', {
|
||||||
|
'claim-preview--large': type === 'large',
|
||||||
|
'claim-preview__empty': true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Empty text={__('Nothing found here. Like big tech ethics.')} />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewNoContent;
|
35
ui/component/claimPreview/claim-preview-no-mature.jsx
Normal file
35
ui/component/claimPreview/claim-preview-no-mature.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewNoMature(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
||||||
|
<div className="media__thumb" />
|
||||||
|
<div
|
||||||
|
className={classnames('claim-preview', {
|
||||||
|
'claim-preview--large': type === 'large',
|
||||||
|
'claim-preview__empty': true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Empty text={__('Mature content hidden by your preferences')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewNoMature;
|
|
@ -22,6 +22,9 @@ import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||||
import PublishPending from 'component/publishPending';
|
import PublishPending from 'component/publishPending';
|
||||||
|
import ClaimPreviewLoading from './claim-preview-loading';
|
||||||
|
import ClaimPreviewNoMature from './claim-preview-no-mature';
|
||||||
|
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -58,6 +61,7 @@ type Props = {
|
||||||
getFile: string => void,
|
getFile: string => void,
|
||||||
customShouldHide?: Claim => boolean,
|
customShouldHide?: Claim => boolean,
|
||||||
showUnresolvedClaim?: boolean,
|
showUnresolvedClaim?: boolean,
|
||||||
|
showNullPlaceholder?: boolean,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
hideActions?: boolean,
|
hideActions?: boolean,
|
||||||
renderActions?: Claim => ?Node,
|
renderActions?: Claim => ?Node,
|
||||||
|
@ -94,6 +98,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
customShouldHide,
|
customShouldHide,
|
||||||
showUnresolvedClaim,
|
showUnresolvedClaim,
|
||||||
|
showNullPlaceholder,
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
hideActions = false,
|
hideActions = false,
|
||||||
renderActions,
|
renderActions,
|
||||||
|
@ -195,29 +200,22 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
}
|
}
|
||||||
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
|
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
|
||||||
|
|
||||||
if (shouldHide) {
|
if (shouldHide && !showNullPlaceholder) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder === 'loading' || claim === undefined || (isResolvingUri && !claim)) {
|
if (placeholder === 'loading' || (isResolvingUri && !claim)) {
|
||||||
return (
|
return <ClaimPreviewLoading isChannel={isChannel} type={type} />;
|
||||||
<li
|
|
||||||
disabled
|
|
||||||
className={classnames('claim-preview__wrapper', {
|
|
||||||
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
|
||||||
'claim-preview__wrapper--inline': type === 'inline',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
|
||||||
<div className="placeholder media__thumb" />
|
|
||||||
<div className="placeholder__wrapper">
|
|
||||||
<div className="placeholder claim-preview__title" />
|
|
||||||
<div className="placeholder media__subtitle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (claim && showNullPlaceholder && shouldHide && nsfw) {
|
||||||
|
return <ClaimPreviewNoMature isChannel={isChannel} type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claim && showNullPlaceholder) {
|
||||||
|
return <ClaimPreviewNoContent isChannel={isChannel} type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && claim === null) {
|
if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && claim === null) {
|
||||||
return <AbandonedChannelPreview uri={uri} type />;
|
return <AbandonedChannelPreview uri={uri} type />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CommentCreate from 'component/commentCreate';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
||||||
import { sortComments } from 'util/comments';
|
import { sortComments } from 'util/comments';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comments: Array<Comment>,
|
comments: Array<Comment>,
|
||||||
|
@ -58,7 +59,7 @@ function CommentList(props: Props) {
|
||||||
Boolean(reactionsById) || !ENABLE_COMMENT_REACTIONS
|
Boolean(reactionsById) || !ENABLE_COMMENT_REACTIONS
|
||||||
);
|
);
|
||||||
const linkedCommentId = linkedComment && linkedComment.comment_id;
|
const linkedCommentId = linkedComment && linkedComment.comment_id;
|
||||||
const hasNoComments = totalComments === 0;
|
const hasNoComments = !totalComments;
|
||||||
const moreBelow = totalComments - end > 0;
|
const moreBelow = totalComments - end > 0;
|
||||||
const isMyComment = (channelId: string): boolean => {
|
const isMyComment = (channelId: string): boolean => {
|
||||||
if (myChannels != null && channelId != null) {
|
if (myChannels != null && channelId != null) {
|
||||||
|
@ -210,7 +211,7 @@ function CommentList(props: Props) {
|
||||||
<>
|
<>
|
||||||
<CommentCreate uri={uri} />
|
<CommentCreate uri={uri} />
|
||||||
|
|
||||||
{!isFetchingComments && hasNoComments && <div className="main--empty">{__('Be the first to comment!')}</div>}
|
{!isFetchingComments && hasNoComments && <Empty text={__('That was pretty deep. What do you think?')} />}
|
||||||
|
|
||||||
<ul className="comments" ref={commentRef}>
|
<ul className="comments" ref={commentRef}>
|
||||||
{comments &&
|
{comments &&
|
||||||
|
|
30
ui/component/common/empty.jsx
Normal file
30
ui/component/common/empty.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Empty extends React.PureComponent<Props> {
|
||||||
|
static defaultProps = {
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { text } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="empty__wrap">
|
||||||
|
<div>
|
||||||
|
{text && (
|
||||||
|
<div className="empty__content">
|
||||||
|
<p className="empty__text">{text}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Empty;
|
|
@ -74,7 +74,7 @@ function FileActions(props: Props) {
|
||||||
push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`);
|
push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`);
|
||||||
doToast({ message: __('A channel is required to repost on %SITE_NAME%', { SITE_NAME }) });
|
doToast({ message: __('A channel is required to repost on %SITE_NAME%', { SITE_NAME }) });
|
||||||
} else {
|
} else {
|
||||||
openModal(MODALS.REPOST, { uri });
|
push(`/$/${PAGES.REPOST_NEW}?from=${encodeURIComponent(uri)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,7 @@ const Header = (props: Props) => {
|
||||||
icon={ICONS.ARROW_LEFT}
|
icon={ICONS.ARROW_LEFT}
|
||||||
/>
|
/>
|
||||||
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
||||||
{authenticated ? (
|
{authenticated || !IS_WEB ? (
|
||||||
<Button
|
<Button
|
||||||
aria-label={__('Your wallet')}
|
aria-label={__('Your wallet')}
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
|
|
45
ui/component/repostCreate/index.js
Normal file
45
ui/component/repostCreate/index.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doHideModal } from 'redux/actions/app';
|
||||||
|
import {
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectTitleForUri,
|
||||||
|
selectBalance,
|
||||||
|
selectMyChannelClaims,
|
||||||
|
doRepost,
|
||||||
|
selectRepostError,
|
||||||
|
selectRepostLoading,
|
||||||
|
doClearRepostError,
|
||||||
|
selectMyClaimsWithoutChannels,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
makeSelectEffectiveAmountForUri,
|
||||||
|
makeSelectIsUriResolving,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import RepostCreate from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
passedRepostClaim: makeSelectClaimForUri(props.name)(state),
|
||||||
|
passedRepostAmount: makeSelectEffectiveAmountForUri(props.name)(state),
|
||||||
|
enteredContentClaim: makeSelectClaimForUri(props.contentUri)(state),
|
||||||
|
enteredRepostClaim: makeSelectClaimForUri(props.repostUri)(state),
|
||||||
|
enteredRepostAmount: makeSelectEffectiveAmountForUri(props.repostUri)(state),
|
||||||
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
error: selectRepostError(state),
|
||||||
|
reposting: selectRepostLoading(state),
|
||||||
|
myClaims: selectMyClaimsWithoutChannels(state),
|
||||||
|
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
|
||||||
|
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doHideModal,
|
||||||
|
doRepost,
|
||||||
|
doClearRepostError,
|
||||||
|
doToast,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
})(RepostCreate);
|
415
ui/component/repostCreate/view.jsx
Normal file
415
ui/component/repostCreate/view.jsx
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import SelectChannel from 'component/selectChannel';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
|
||||||
|
import HelpLink from 'component/common/help-link';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
doToast: ({ message: string }) => void,
|
||||||
|
doClearRepostError: () => void,
|
||||||
|
doRepost: StreamRepostOptions => Promise<*>,
|
||||||
|
title: string,
|
||||||
|
claim?: StreamClaim,
|
||||||
|
enteredContentClaim?: StreamClaim,
|
||||||
|
balance: number,
|
||||||
|
channels: ?Array<ChannelClaim>,
|
||||||
|
doCheckPublishNameAvailability: string => Promise<*>,
|
||||||
|
error: ?string,
|
||||||
|
reposting: boolean,
|
||||||
|
uri: string,
|
||||||
|
name: string,
|
||||||
|
contentUri: string,
|
||||||
|
setRepostUri: string => void,
|
||||||
|
setContentUri: string => void,
|
||||||
|
doCheckPendingClaims: () => void,
|
||||||
|
redirectUri?: string,
|
||||||
|
passedRepostAmount: number,
|
||||||
|
enteredRepostAmount: number,
|
||||||
|
isResolvingPassedRepost: boolean,
|
||||||
|
isResolvingEnteredRepost: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
function RepostCreate(props: Props) {
|
||||||
|
const {
|
||||||
|
redirectUri,
|
||||||
|
doToast,
|
||||||
|
doClearRepostError,
|
||||||
|
doRepost,
|
||||||
|
claim,
|
||||||
|
enteredContentClaim,
|
||||||
|
balance,
|
||||||
|
channels,
|
||||||
|
reposting,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
uri, // ?from
|
||||||
|
name, // ?to
|
||||||
|
contentUri,
|
||||||
|
setRepostUri,
|
||||||
|
setContentUri,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
enteredRepostAmount,
|
||||||
|
passedRepostAmount,
|
||||||
|
isResolvingPassedRepost,
|
||||||
|
isResolvingEnteredRepost,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const defaultName = name || (claim && claim.name) || '';
|
||||||
|
const contentClaimId = claim && claim.claim_id;
|
||||||
|
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
|
||||||
|
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel', 'anonymous');
|
||||||
|
|
||||||
|
const [repostBid, setRepostBid] = React.useState(0.01);
|
||||||
|
const [repostBidError, setRepostBidError] = React.useState(undefined);
|
||||||
|
const [takeoverAmount, setTakeoverAmount] = React.useState(0);
|
||||||
|
const [enteredRepostName, setEnteredRepostName] = React.useState(defaultName);
|
||||||
|
const [available, setAvailable] = React.useState(true);
|
||||||
|
const [enteredContent, setEnteredContentUri] = React.useState(undefined);
|
||||||
|
const [contentError, setContentError] = React.useState('');
|
||||||
|
|
||||||
|
const { replace, goBack } = useHistory();
|
||||||
|
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
|
||||||
|
const repostUrlName = `lbry://${
|
||||||
|
!repostChannel || repostChannel === CHANNEL_NEW || repostChannel === 'anonymous' ? '' : `${repostChannel}/`
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const contentFirstRender = React.useRef(true);
|
||||||
|
const setAutoRepostBid = amount => {
|
||||||
|
if (balance > amount) {
|
||||||
|
if (amount > 5) {
|
||||||
|
setRepostBid(Number(amount.toFixed(2)));
|
||||||
|
} else {
|
||||||
|
setRepostBid(5);
|
||||||
|
}
|
||||||
|
} else if (balance) {
|
||||||
|
setRepostBid(0.01);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSearchUri(value) {
|
||||||
|
const WEB_DEV_PREFIX = `${URL_DEV}/`;
|
||||||
|
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
|
||||||
|
const WEB_PROD_PREFIX = `${SITE_URL}/`;
|
||||||
|
const ODYSEE_PREFIX = `https://odysee.com/`;
|
||||||
|
const includesLbryTvProd = value.includes(WEB_PROD_PREFIX);
|
||||||
|
const includesOdysee = value.includes(ODYSEE_PREFIX);
|
||||||
|
const includesLbryTvLocal = value.includes(WEB_LOCAL_PREFIX);
|
||||||
|
const includesLbryTvDev = value.includes(WEB_DEV_PREFIX);
|
||||||
|
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd || includesOdysee;
|
||||||
|
const isLbryUrl = value.startsWith('lbry://') && value !== 'lbry://';
|
||||||
|
const error = '';
|
||||||
|
|
||||||
|
const addLbryIfNot = term => {
|
||||||
|
return term.startsWith('lbry://') ? term : `lbry://${term}`;
|
||||||
|
};
|
||||||
|
if (wasCopiedFromWeb) {
|
||||||
|
let prefix = WEB_PROD_PREFIX;
|
||||||
|
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
|
||||||
|
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
|
||||||
|
if (includesOdysee) prefix = ODYSEE_PREFIX;
|
||||||
|
|
||||||
|
let query = (value && value.slice(prefix.length).replace(/:/g, '#')) || '';
|
||||||
|
try {
|
||||||
|
const lbryUrl = `lbry://${query}`;
|
||||||
|
parseURI(lbryUrl);
|
||||||
|
return [lbryUrl, null];
|
||||||
|
} catch (e) {
|
||||||
|
return [query, 'error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLbryUrl) {
|
||||||
|
if (value.startsWith('@')) {
|
||||||
|
if (isNameValid(value.slice(1))) {
|
||||||
|
return [value, null];
|
||||||
|
} else {
|
||||||
|
return [value, error];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [addLbryIfNot(value), null];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const isValid = isURIValid(value);
|
||||||
|
if (isValid) {
|
||||||
|
let uri;
|
||||||
|
try {
|
||||||
|
uri = normalizeURI(value);
|
||||||
|
} catch (e) {
|
||||||
|
return [value, null];
|
||||||
|
}
|
||||||
|
return [uri, null];
|
||||||
|
} else {
|
||||||
|
return [value, null];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return [value, 'error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// repostName
|
||||||
|
let repostNameError;
|
||||||
|
if (!enteredRepostName) {
|
||||||
|
repostNameError = __('A name is required');
|
||||||
|
} else if (!isNameValid(enteredRepostName, false)) {
|
||||||
|
repostNameError = INVALID_NAME_ERROR;
|
||||||
|
} else if (!available) {
|
||||||
|
repostNameError = __('You already have a claim with this name.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentName
|
||||||
|
let contentNameError;
|
||||||
|
if (!enteredContent && enteredContent !== undefined) {
|
||||||
|
contentNameError = __('A name is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// repostChannel
|
||||||
|
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!repostChannel && channelStrings) {
|
||||||
|
const channels = channelStrings.split(',');
|
||||||
|
const newChannelUrl = channels[0];
|
||||||
|
const { claimName } = parseURI(newChannelUrl);
|
||||||
|
setRepostChannel(claimName);
|
||||||
|
}
|
||||||
|
}, [channelStrings]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
||||||
|
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
||||||
|
}
|
||||||
|
}, [enteredRepostName, doCheckPublishNameAvailability]);
|
||||||
|
|
||||||
|
// takeover amount, bid suggestion
|
||||||
|
React.useEffect(() => {
|
||||||
|
const repostTakeoverAmount = Number(enteredRepostAmount)
|
||||||
|
? Number(enteredRepostAmount) + 0.01
|
||||||
|
: Number(passedRepostAmount) + 0.01;
|
||||||
|
|
||||||
|
if (repostTakeoverAmount) {
|
||||||
|
setTakeoverAmount(Number(repostTakeoverAmount.toFixed(2)));
|
||||||
|
setAutoRepostBid(repostTakeoverAmount);
|
||||||
|
}
|
||||||
|
}, [setTakeoverAmount, enteredRepostAmount, passedRepostAmount]);
|
||||||
|
|
||||||
|
// repost bid error
|
||||||
|
React.useEffect(() => {
|
||||||
|
let rBidError;
|
||||||
|
if (repostBid === 0) {
|
||||||
|
rBidError = __('Deposit cannot be 0');
|
||||||
|
} else if (balance === repostBid) {
|
||||||
|
rBidError = __('Please decrease your deposit to account for transaction fees');
|
||||||
|
} else if (balance < repostBid) {
|
||||||
|
rBidError = __('Deposit cannot be higher than your balance');
|
||||||
|
} else if (repostBid < MINIMUM_PUBLISH_BID) {
|
||||||
|
rBidError = __('Your deposit must be higher');
|
||||||
|
}
|
||||||
|
setRepostBidError(rBidError);
|
||||||
|
}, [setRepostBidError, repostBidError, repostBid]);
|
||||||
|
|
||||||
|
// setContentUri given enteredUri
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!enteredContent && !contentFirstRender.current) {
|
||||||
|
setContentError(__('A name is required'));
|
||||||
|
}
|
||||||
|
if (enteredContent) {
|
||||||
|
contentFirstRender.current = false;
|
||||||
|
const [searchContent, error] = getSearchUri(enteredContent);
|
||||||
|
if (error) {
|
||||||
|
setContentError(__('Something not quite right..'));
|
||||||
|
} else {
|
||||||
|
setContentError('');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { streamName, channelName, isChannel } = parseURI(searchContent);
|
||||||
|
if (!isChannel && streamName && isNameValid(streamName)) {
|
||||||
|
// contentNameValid = true;
|
||||||
|
setContentUri(searchContent);
|
||||||
|
} else if (isChannel && channelName && isNameValid(channelName)) {
|
||||||
|
// contentNameValid = true;
|
||||||
|
setContentUri(searchContent);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (enteredContent !== '@') setContentError('');
|
||||||
|
setContentUri(``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [enteredContent, setContentUri, setContentError, parseURI, isNameValid]);
|
||||||
|
|
||||||
|
// setRepostName
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (enteredRepostName) {
|
||||||
|
let isValid = false;
|
||||||
|
try {
|
||||||
|
parseURI(enteredRepostName);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
setRepostUri(enteredRepostName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [enteredRepostName, setRepostUri, parseURI]);
|
||||||
|
|
||||||
|
const repostClaimId = contentClaimId || enteredClaimId;
|
||||||
|
|
||||||
|
const getRedirect = (entered, passed, redirect) => {
|
||||||
|
if (redirect) {
|
||||||
|
return redirect;
|
||||||
|
} else if (entered) {
|
||||||
|
try {
|
||||||
|
const { claimName } = parseURI(entered);
|
||||||
|
return `/${claimName}`;
|
||||||
|
} catch (e) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
} else if (passed) {
|
||||||
|
try {
|
||||||
|
const { claimName } = parseURI(passed);
|
||||||
|
return `/${claimName}`;
|
||||||
|
} catch (e) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
|
||||||
|
if (enteredRepostName && repostBid && repostClaimId) {
|
||||||
|
doRepost({
|
||||||
|
name: enteredRepostName,
|
||||||
|
bid: creditsToString(repostBid),
|
||||||
|
channel_id: channelToRepostTo ? channelToRepostTo.claim_id : undefined,
|
||||||
|
claim_id: repostClaimId,
|
||||||
|
}).then((repostClaim: StreamClaim) => {
|
||||||
|
doCheckPendingClaims();
|
||||||
|
analytics.apiLogPublish(repostClaim);
|
||||||
|
doToast({ message: __('Woohoo! Successfully reposted this claim.') });
|
||||||
|
replace(getRedirect(contentUri, uri, redirectUri));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelIt() {
|
||||||
|
doClearRepostError();
|
||||||
|
goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
actions={
|
||||||
|
<div>
|
||||||
|
{uri && (
|
||||||
|
<fieldset-section>
|
||||||
|
<ClaimPreview key={uri} uri={uri} actions={''} type={'inline'} showNullPlaceholder />
|
||||||
|
</fieldset-section>
|
||||||
|
)}
|
||||||
|
{!uri && name && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
label={'Content to repost'}
|
||||||
|
type="text"
|
||||||
|
name="content_url"
|
||||||
|
value={enteredContent}
|
||||||
|
error={contentError}
|
||||||
|
onChange={event => setEnteredContentUri(event.target.value)}
|
||||||
|
placeholder={__('Enter a name or %domain% URL', { domain: SITE_URL })}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!uri && (
|
||||||
|
<fieldset-section>
|
||||||
|
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
|
||||||
|
</fieldset-section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<React.Fragment>
|
||||||
|
<fieldset-section>
|
||||||
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
|
<fieldset-section>
|
||||||
|
<label htmlFor="auth_first_channel">
|
||||||
|
{repostNameError ? (
|
||||||
|
<span className="error__text">{repostNameError}</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{__('Repost URL')}
|
||||||
|
<HelpLink href="https://lbry.com/faq/naming" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<div className="form-field__prefix">{repostUrlName}</div>
|
||||||
|
</fieldset-section>
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
name="repost_name"
|
||||||
|
value={enteredRepostName}
|
||||||
|
onChange={event => setEnteredRepostName(event.target.value)}
|
||||||
|
placeholder={__('Do a thing')}
|
||||||
|
/>
|
||||||
|
</fieldset-group>
|
||||||
|
</fieldset-section>
|
||||||
|
<SelectChannel
|
||||||
|
label={__('Channel to repost on')}
|
||||||
|
hideNew
|
||||||
|
channel={repostChannel}
|
||||||
|
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
type="number"
|
||||||
|
name="repost_bid"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
placeholder="0.123"
|
||||||
|
className="form-field--price-amount"
|
||||||
|
label={<LbcSymbol postfix={__('Support')} size={14} />}
|
||||||
|
value={repostBid}
|
||||||
|
error={repostBidError}
|
||||||
|
helper={__('Winning amount: %amount%', {
|
||||||
|
amount: Number(takeoverAmount).toFixed(2),
|
||||||
|
})}
|
||||||
|
disabled={!enteredRepostName || resolvingRepost}
|
||||||
|
onChange={event => setRepostBid(event.target.value)}
|
||||||
|
onWheel={e => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
icon={ICONS.REPOST}
|
||||||
|
disabled={
|
||||||
|
resolvingRepost ||
|
||||||
|
reposting ||
|
||||||
|
repostBidError ||
|
||||||
|
repostNameError ||
|
||||||
|
((!uri || enteredContent) && contentNameError) ||
|
||||||
|
(!uri && !enteredContentClaim)
|
||||||
|
}
|
||||||
|
button="primary"
|
||||||
|
label={reposting ? __('Reposting') : __('Repost')}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Cancel')} onClick={cancelIt} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepostCreate;
|
|
@ -43,6 +43,7 @@ import CreatorDashboard from 'page/creatorDashboard';
|
||||||
import RewardsVerifyPage from 'page/rewardsVerify';
|
import RewardsVerifyPage from 'page/rewardsVerify';
|
||||||
import CheckoutPage from 'page/checkoutPage';
|
import CheckoutPage from 'page/checkoutPage';
|
||||||
import ChannelNew from 'page/channelNew';
|
import ChannelNew from 'page/channelNew';
|
||||||
|
import RepostNew from 'page/repost';
|
||||||
import BuyPage from 'page/buy';
|
import BuyPage from 'page/buy';
|
||||||
import NotificationsPage from 'page/notifications';
|
import NotificationsPage from 'page/notifications';
|
||||||
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
||||||
|
@ -262,6 +263,7 @@ function AppRouter(props: Props) {
|
||||||
/>
|
/>
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.REPOST_NEW}`} component={RepostNew} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.UPLOADS}`} component={FileListPublished} />
|
<PrivateRoute {...props} path={`/$/${PAGES.UPLOADS}`} component={FileListPublished} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.UPLOAD}`} component={PublishPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.UPLOAD}`} component={PublishPage} />
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doResolveUris } from 'lbry-redux';
|
import { doResolveUris, doClearPublish, doPrepareEdit, selectPendingIds } from 'lbry-redux';
|
||||||
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
||||||
import SearchTopClaim from './view';
|
import SearchTopClaim from './view';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
winningUri: makeSelectWinningUriForQuery(props.query)(state),
|
winningUri: makeSelectWinningUriForQuery(props.query)(state),
|
||||||
|
pendingIds: selectPendingIds(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
const perform = dispatch => ({
|
||||||
doResolveUris,
|
beginPublish: name => {
|
||||||
})(SearchTopClaim);
|
dispatch(doClearPublish());
|
||||||
|
dispatch(doPrepareEdit({ name }));
|
||||||
|
dispatch(push(`/$/${PAGES.UPLOAD}`));
|
||||||
|
},
|
||||||
|
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(SearchTopClaim);
|
||||||
|
|
|
@ -6,8 +6,11 @@ import { parseURI } from 'lbry-redux';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
||||||
import HelpLink from 'component/common/help-link';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
import { DOMAIN } from 'config';
|
||||||
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
query: string,
|
query: string,
|
||||||
|
@ -15,20 +18,26 @@ type Props = {
|
||||||
doResolveUris: (Array<string>) => void,
|
doResolveUris: (Array<string>) => void,
|
||||||
hideLink?: boolean,
|
hideLink?: boolean,
|
||||||
setChannelActive: boolean => void,
|
setChannelActive: boolean => void,
|
||||||
|
beginPublish: string => void,
|
||||||
|
pendingIds: Array<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchTopClaim(props: Props) {
|
export default function SearchTopClaim(props: Props) {
|
||||||
const { doResolveUris, query = '', winningUri, hideLink = false, setChannelActive } = props;
|
const { doResolveUris, query = '', winningUri, hideLink = false, setChannelActive, beginPublish } = props;
|
||||||
const uriFromQuery = `lbry://${query}`;
|
const uriFromQuery = `lbry://${query}`;
|
||||||
|
const { push } = useHistory();
|
||||||
|
let name;
|
||||||
let channelUriFromQuery;
|
let channelUriFromQuery;
|
||||||
let winningUriIsChannel;
|
let winningUriIsChannel;
|
||||||
try {
|
try {
|
||||||
const { isChannel } = parseURI(uriFromQuery);
|
const { isChannel, streamName, channelName } = parseURI(uriFromQuery);
|
||||||
const { isChannel: winnerIsChannel } = parseURI(winningUri);
|
const { isChannel: winnerIsChannel } = parseURI(winningUri);
|
||||||
winningUriIsChannel = winnerIsChannel;
|
winningUriIsChannel = winnerIsChannel;
|
||||||
if (!isChannel) {
|
if (!isChannel) {
|
||||||
channelUriFromQuery = `lbry://@${query}`;
|
channelUriFromQuery = `lbry://@${query}`;
|
||||||
|
name = streamName;
|
||||||
|
} else {
|
||||||
|
name = channelName;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
|
@ -51,33 +60,68 @@ export default function SearchTopClaim(props: Props) {
|
||||||
}
|
}
|
||||||
}, [doResolveUris, uriFromQuery, channelUriFromQuery]);
|
}, [doResolveUris, uriFromQuery, channelUriFromQuery]);
|
||||||
|
|
||||||
if (!winningUri) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="search">
|
<section className="search">
|
||||||
<header className="search__header">
|
<header className="search__header">
|
||||||
<div className="claim-preview__actions--header">
|
{winningUri && (
|
||||||
<span className="media__uri">
|
<div className="claim-preview__actions--header">
|
||||||
{__('Most supported')}
|
<a
|
||||||
<HelpLink href="https://lbry.com/faq/tipping" />
|
className="media__uri"
|
||||||
</span>
|
href="https://lbry.com/faq/trending"
|
||||||
</div>
|
title={__('Learn more about LBRY Credits on %DOMAIN%', { DOMAIN })}
|
||||||
<div className="card">
|
>
|
||||||
<ClaimPreview
|
<I18nMessage
|
||||||
hideRepostLabel
|
tokens={{
|
||||||
uri={winningUri}
|
lbc: <LbcSymbol />,
|
||||||
type="large"
|
}}
|
||||||
placeholder="publish"
|
>
|
||||||
properties={claim => (
|
Most supported %lbc%
|
||||||
<span className="claim-preview__custom-properties">
|
</I18nMessage>
|
||||||
<ClaimEffectiveAmount uri={winningUri} />
|
</a>
|
||||||
</span>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
{winningUri && (
|
||||||
</div>
|
<div className="card">
|
||||||
{!hideLink && (
|
<ClaimPreview
|
||||||
|
hideRepostLabel
|
||||||
|
uri={winningUri}
|
||||||
|
type="large"
|
||||||
|
placeholder="publish"
|
||||||
|
properties={claim => (
|
||||||
|
<span className="claim-preview__custom-properties">
|
||||||
|
<ClaimEffectiveAmount uri={winningUri} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!winningUri && uriFromQuery && (
|
||||||
|
<div className="empty empty--centered">
|
||||||
|
<Yrbl
|
||||||
|
type="happy"
|
||||||
|
title={__('Whoa!')}
|
||||||
|
small
|
||||||
|
subtitle={
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
repost: (
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
onClick={() => push(`/$/${PAGES.REPOST_NEW}?to=${name}`)}
|
||||||
|
label={__('Repost')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
publish: <Button button="link" onClick={() => beginPublish(name)} label={'publish'} />,
|
||||||
|
name: <strong>name</strong>,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
You have found the edge of the internet. %repost% or %publish% your stuff here to claim this spot.
|
||||||
|
</I18nMessage>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!hideLink && winningUri && (
|
||||||
<div className="section__actions--between section__actions--no-margin">
|
<div className="section__actions--between section__actions--no-margin">
|
||||||
<span />
|
<span />
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Props = {
|
||||||
className?: string,
|
className?: string,
|
||||||
actions?: Node,
|
actions?: Node,
|
||||||
alwaysShow?: boolean,
|
alwaysShow?: boolean,
|
||||||
|
small: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const yrblTypes = {
|
const yrblTypes = {
|
||||||
|
@ -24,7 +25,7 @@ export default class extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, subtitle, type, className, actions, alwaysShow = false } = this.props;
|
const { title, subtitle, type, className, actions, small, alwaysShow = false } = this.props;
|
||||||
|
|
||||||
const image = yrblTypes[type];
|
const image = yrblTypes[type];
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export default class extends React.PureComponent<Props> {
|
||||||
<div className="yrbl__wrap">
|
<div className="yrbl__wrap">
|
||||||
<img
|
<img
|
||||||
alt="Friendly gerbil"
|
alt="Friendly gerbil"
|
||||||
className={classnames('yrbl', className, {
|
className={classnames(small ? 'yrbl--small' : 'yrbl', className, {
|
||||||
'yrbl--always-show': alwaysShow,
|
'yrbl--always-show': alwaysShow,
|
||||||
})}
|
})}
|
||||||
src={`${image}`}
|
src={`${image}`}
|
||||||
|
|
|
@ -19,6 +19,7 @@ exports.GET_CREDITS = 'getcredits';
|
||||||
exports.REPORT = 'report';
|
exports.REPORT = 'report';
|
||||||
exports.REWARDS = 'rewards';
|
exports.REWARDS = 'rewards';
|
||||||
exports.REWARDS_VERIFY = 'rewards/verify';
|
exports.REWARDS_VERIFY = 'rewards/verify';
|
||||||
|
exports.REPOST_NEW = 'repost';
|
||||||
exports.SEND = 'send';
|
exports.SEND = 'send';
|
||||||
exports.SETTINGS = 'settings';
|
exports.SETTINGS = 'settings';
|
||||||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { parseURI, isNameValid, creditsToString } from 'lbry-redux';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import LbcSymbol from '../../component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doHideModal: () => void,
|
doHideModal: () => void,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
balance: number,
|
balance: number,
|
||||||
totalRewardValue: number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishPage(props: Props) {
|
function PublishPage(props: Props) {
|
||||||
|
|
15
ui/page/repost/index.js
Normal file
15
ui/page/repost/index.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { doResolveUri, selectBalance } from 'lbry-redux';
|
||||||
|
|
||||||
|
import RepostPage from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(RepostPage);
|
77
ui/page/repost/view.jsx
Normal file
77
ui/page/repost/view.jsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import RepostCreate from 'component/repostCreate';
|
||||||
|
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||||
|
import useThrottle from 'effects/use-throttle';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
balance: number,
|
||||||
|
resolveUri: string => void,
|
||||||
|
};
|
||||||
|
function RepostPage(props: Props) {
|
||||||
|
const { balance, resolveUri } = props;
|
||||||
|
|
||||||
|
const REPOST_FROM = 'from';
|
||||||
|
const REPOST_TO = 'to';
|
||||||
|
const REDIRECT = 'redirect';
|
||||||
|
const {
|
||||||
|
location: { search },
|
||||||
|
} = useHistory();
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const repostFrom = urlParams.get(REPOST_FROM);
|
||||||
|
const redirectUri = urlParams.get(REDIRECT);
|
||||||
|
const repostTo = urlParams.get(REPOST_TO);
|
||||||
|
const [contentUri, setContentUri] = React.useState('');
|
||||||
|
const [repostUri, setRepostUri] = React.useState('');
|
||||||
|
const throttledContentValue = useThrottle(contentUri, 500);
|
||||||
|
const throttledRepostValue = useThrottle(repostUri, 500);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (throttledContentValue) {
|
||||||
|
resolveUri(throttledContentValue);
|
||||||
|
}
|
||||||
|
}, [throttledContentValue, resolveUri]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (throttledRepostValue) {
|
||||||
|
resolveUri(throttledRepostValue);
|
||||||
|
}
|
||||||
|
}, [throttledRepostValue, resolveUri]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (repostTo) {
|
||||||
|
resolveUri(repostTo);
|
||||||
|
}
|
||||||
|
}, [repostTo, resolveUri]);
|
||||||
|
|
||||||
|
const decodedFrom = repostFrom && decodeURIComponent(repostFrom);
|
||||||
|
return (
|
||||||
|
<Page
|
||||||
|
noFooter
|
||||||
|
noSideNavigation
|
||||||
|
backout={{
|
||||||
|
title: __('Repost'),
|
||||||
|
backLabel: __('Back'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{balance === 0 && <YrblWalletEmpty />}
|
||||||
|
<div className={classnames({ 'card--disabled': balance === 0 })}>
|
||||||
|
<RepostCreate
|
||||||
|
uri={decodedFrom}
|
||||||
|
name={repostTo}
|
||||||
|
redirectUri={redirectUri}
|
||||||
|
repostUri={repostUri}
|
||||||
|
contentUri={contentUri}
|
||||||
|
setContentUri={setContentUri}
|
||||||
|
setRepostUri={setRepostUri}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepostPage;
|
|
@ -11,6 +11,7 @@ import Ads from 'web/component/ads';
|
||||||
import SearchTopClaim from 'component/searchTopClaim';
|
import SearchTopClaim from 'component/searchTopClaim';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
|
||||||
type AdditionalOptions = {
|
type AdditionalOptions = {
|
||||||
isBackgroundSearch: boolean,
|
isBackgroundSearch: boolean,
|
||||||
|
@ -97,7 +98,6 @@ export default function SearchPage(props: Props) {
|
||||||
{urlQuery && (
|
{urlQuery && (
|
||||||
<>
|
<>
|
||||||
{isValid && <SearchTopClaim query={modifiedUrlQuery} />}
|
{isValid && <SearchTopClaim query={modifiedUrlQuery} />}
|
||||||
|
|
||||||
<ClaimList
|
<ClaimList
|
||||||
uris={uris}
|
uris={uris}
|
||||||
loading={isSearching}
|
loading={isSearching}
|
||||||
|
@ -121,6 +121,7 @@ export default function SearchPage(props: Props) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{isSearching && new Array(5).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||||
|
|
||||||
<div className="main--empty help">{__('These search results are provided by LBRY, Inc.')}</div>
|
<div className="main--empty help">{__('These search results are provided by LBRY, Inc.')}</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import TopPage from './view';
|
import TopPage from './view';
|
||||||
|
import { doClearPublish, doPrepareEdit, doResolveUris } from 'lbry-redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { search } = props.location;
|
const { search } = props.location;
|
||||||
|
@ -11,4 +14,13 @@ const select = (state, props) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(select)(TopPage);
|
const perform = dispatch => ({
|
||||||
|
beginPublish: name => {
|
||||||
|
dispatch(doClearPublish());
|
||||||
|
dispatch(doPrepareEdit({ name }));
|
||||||
|
dispatch(push(`/$/${PAGES.UPLOAD}`));
|
||||||
|
},
|
||||||
|
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(TopPage);
|
||||||
|
|
|
@ -7,22 +7,42 @@ import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
||||||
import SearchTopClaim from 'component/searchTopClaim';
|
import SearchTopClaim from 'component/searchTopClaim';
|
||||||
import { ORDER_BY_TOP, FRESH_ALL } from 'constants/claim_search';
|
import { ORDER_BY_TOP, FRESH_ALL } from 'constants/claim_search';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
|
beginPublish: string => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function TopPage(props: Props) {
|
function TopPage(props: Props) {
|
||||||
const { name } = props;
|
const { name, beginPublish } = props;
|
||||||
const [channelActive, setChannelActive] = React.useState(false);
|
const [channelActive, setChannelActive] = React.useState(false);
|
||||||
|
// if the query was actually '@name', still offer repost for 'name'
|
||||||
|
const queryName = name[0] === '@' ? name.slice(1) : name;
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<SearchTopClaim query={name} hideLink setChannelActive={setChannelActive} />
|
<SearchTopClaim query={name} hideLink setChannelActive={setChannelActive} />
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
name={channelActive ? `@${name}` : name}
|
name={channelActive ? `@${queryName}` : queryName}
|
||||||
defaultFreshness={FRESH_ALL}
|
defaultFreshness={FRESH_ALL}
|
||||||
defaultOrderBy={ORDER_BY_TOP}
|
defaultOrderBy={ORDER_BY_TOP}
|
||||||
|
meta={
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
repost: (
|
||||||
|
<Button
|
||||||
|
button="secondary"
|
||||||
|
navigate={`/$/${PAGES.REPOST_NEW}?to=${queryName}`}
|
||||||
|
label={__('Repost Here')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
publish: <Button button="secondary" onClick={() => beginPublish(queryName)} label={'Publish Here'} />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
%repost% %publish%
|
||||||
|
</I18nMessage>
|
||||||
|
}
|
||||||
includeSupportAction
|
includeSupportAction
|
||||||
renderProperties={claim => (
|
renderProperties={claim => (
|
||||||
<span className="claim-preview__custom-properties">
|
<span className="claim-preview__custom-properties">
|
||||||
|
@ -33,7 +53,7 @@ function TopPage(props: Props) {
|
||||||
header={
|
header={
|
||||||
<div className="claim-search__menu-group">
|
<div className="claim-search__menu-group">
|
||||||
<Button
|
<Button
|
||||||
label={name}
|
label={queryName}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => setChannelActive(false)}
|
onClick={() => setChannelActive(false)}
|
||||||
className={classnames('button-toggle', {
|
className={classnames('button-toggle', {
|
||||||
|
@ -41,7 +61,7 @@ function TopPage(props: Props) {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
label={`@${name}`}
|
label={`@${queryName}`}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => setChannelActive(true)}
|
onClick={() => setChannelActive(true)}
|
||||||
className={classnames('button-toggle', {
|
className={classnames('button-toggle', {
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { getSearchQueryString } from 'util/query-params';
|
import { getSearchQueryString } from 'util/query-params';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { parseURI, makeSelectClaimForUri, makeSelectClaimIsNsfw, buildURI, SETTINGS, isClaimNsfw } from 'lbry-redux';
|
import {
|
||||||
|
parseURI,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectClaimIsNsfw,
|
||||||
|
buildURI,
|
||||||
|
SETTINGS,
|
||||||
|
isClaimNsfw,
|
||||||
|
makeSelectPendingClaimForUri,
|
||||||
|
} from 'lbry-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
type State = { search: SearchState };
|
type State = { search: SearchState };
|
||||||
|
@ -79,7 +87,6 @@ export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||||
recommendedContent = searchUris;
|
recommendedContent = searchUris;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return recommendedContent;
|
return recommendedContent;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -97,11 +104,13 @@ export const makeSelectWinningUriForQuery = (query: string) => {
|
||||||
|
|
||||||
return createSelector(
|
return createSelector(
|
||||||
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
||||||
|
makeSelectPendingClaimForUri(uriFromQuery),
|
||||||
makeSelectClaimForUri(uriFromQuery),
|
makeSelectClaimForUri(uriFromQuery),
|
||||||
makeSelectClaimForUri(channelUriFromQuery),
|
makeSelectClaimForUri(channelUriFromQuery),
|
||||||
(matureEnabled, claim1, claim2) => {
|
(matureEnabled, pendingClaim, claim1, claim2) => {
|
||||||
const claim1Mature = claim1 && isClaimNsfw(claim1);
|
const claim1Mature = claim1 && isClaimNsfw(claim1);
|
||||||
const claim2Mature = claim2 && isClaimNsfw(claim2);
|
const claim2Mature = claim2 && isClaimNsfw(claim2);
|
||||||
|
let pendingAmount = pendingClaim && pendingClaim.amount;
|
||||||
|
|
||||||
if (!claim1 && !claim2) {
|
if (!claim1 && !claim2) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -124,7 +133,13 @@ export const makeSelectWinningUriForQuery = (query: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Number(effectiveAmount1) > Number(effectiveAmount2) ? claim1.canonical_url : claim2.canonical_url;
|
const returnBeforePending =
|
||||||
|
Number(effectiveAmount1) > Number(effectiveAmount2) ? claim1.canonical_url : claim2.canonical_url;
|
||||||
|
if (pendingAmount && pendingAmount > effectiveAmount1 && pendingAmount > effectiveAmount2) {
|
||||||
|
return pendingAmount.permanent_url;
|
||||||
|
} else {
|
||||||
|
return returnBeforePending;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,3 +58,4 @@
|
||||||
@import 'component/tags';
|
@import 'component/tags';
|
||||||
@import 'component/wunderbar';
|
@import 'component/wunderbar';
|
||||||
@import 'component/yrbl';
|
@import 'component/yrbl';
|
||||||
|
@import 'component/empty';
|
||||||
|
|
|
@ -98,6 +98,12 @@
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media__thumb-placeholder-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(6rem);
|
@include handleChannelGif(6rem);
|
||||||
}
|
}
|
||||||
|
@ -115,29 +121,47 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(5rem);
|
@include handleChannelGif(4rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.claim-preview--large {
|
.claim-preview--large {
|
||||||
border: none;
|
border: none;
|
||||||
|
min-height: 8rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
width: 14rem;
|
width: 14rem;
|
||||||
@include handleClaimListGifThumbnail(14rem);
|
@include handleClaimListGifThumbnail(14rem);
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
width: 5rem;
|
min-height: 5rem;
|
||||||
|
width: 8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
|
width: 7.5rem;
|
||||||
@include handleChannelGif(7.5rem);
|
@include handleChannelGif(7.5rem);
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
width: 5rem;
|
||||||
|
@include handleChannelGif(5rem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +225,9 @@
|
||||||
|
|
||||||
.claim-preview__custom-properties {
|
.claim-preview__custom-properties {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview-metadata {
|
.claim-preview-metadata {
|
||||||
|
@ -523,3 +550,7 @@
|
||||||
.claim-link {
|
.claim-link {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview__null-label {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -97,3 +97,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.claim-search__menu-group--between {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
.button-toggle:last-of-type {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
ui/scss/component/_empty.scss
Normal file
21
ui/scss/component/_empty.scss
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.empty__wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty__text {
|
||||||
|
color: var(--color-text-empty);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty__content {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
|
@ -34,6 +34,16 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yrbl--small {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
display: block;
|
||||||
|
height: 8rem;
|
||||||
|
margin-right: calc(var(--spacing-xl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.yrbl__content {
|
.yrbl__content {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@
|
||||||
--color-text: #d8d8d8;
|
--color-text: #d8d8d8;
|
||||||
--color-text-error: #f5748c;
|
--color-text-error: #f5748c;
|
||||||
--color-text-help-warning: #f5ec74;
|
--color-text-help-warning: #f5ec74;
|
||||||
--color-text-empty: #bbbbbb;
|
--color-text-empty: var(--color-text-subtitle);
|
||||||
|
|
||||||
--color-text-help: #bbbbbb;
|
--color-text-help: #bbbbbb;
|
||||||
--color-text-subtitle: #9fafc0;
|
--color-text-subtitle: #9fafc0;
|
||||||
--color-text-warning: #212529;
|
--color-text-warning: #212529;
|
||||||
|
@ -74,7 +75,7 @@
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
--color-input: #f4f4f5;
|
--color-input: #f4f4f5;
|
||||||
--color-input-label: #d4d4d4;
|
--color-input-label: #a7a7a7;
|
||||||
--color-input-placeholder: #f4f4f5;
|
--color-input-placeholder: #f4f4f5;
|
||||||
--color-input-bg: #464d53;
|
--color-input-bg: #464d53;
|
||||||
--color-input-bg-copyable: #4f5861;
|
--color-input-bg-copyable: #4f5861;
|
||||||
|
|
|
@ -7498,9 +7498,9 @@ lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#92a4263c908f6df5727c379a3b3348935891d806:
|
lbry-redux@lbryio/lbry-redux#664df6237d777d7cce31e58479bc98563ea98cb9:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/92a4263c908f6df5727c379a3b3348935891d806"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/664df6237d777d7cce31e58479bc98563ea98cb9"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue