recommended content v1 #1817

Merged
neb-b merged 7 commits from next-video into master 2018-07-31 21:42:26 +02:00
25 changed files with 343 additions and 183 deletions

View file

@ -38,6 +38,7 @@
"import/prefer-default-export": 0, "import/prefer-default-export": 0,
"no-return-assign": 0, "no-return-assign": 0,
"react/require-default-props": 0, "react/require-default-props": 0,
"react/jsx-closing-tag-location": 0 "react/jsx-closing-tag-location": 0,
"jsx-a11y/no-noninteractive-element-to-interactive-role": 0
} }
} }

2
.gitignore vendored
View file

@ -5,4 +5,4 @@
yarn-error.log yarn-error.log
package-lock.json package-lock.json
.idea/ .idea/
/build/daemon.ver /build/daemon*

View file

@ -11,9 +11,10 @@ type Props = {
showPlus: boolean, showPlus: boolean,
isEstimate?: boolean, isEstimate?: boolean,
large?: boolean, large?: boolean,
plain?: boolean, showLBC?: boolean,
fee?: boolean, fee?: boolean,
noStyle?: boolean, inheritStyle?: boolean,
filePage?: boolean,
}; };
class CreditAmount extends React.PureComponent<Props> { class CreditAmount extends React.PureComponent<Props> {
@ -22,6 +23,7 @@ class CreditAmount extends React.PureComponent<Props> {
showFree: false, showFree: false,
showFullPrice: false, showFullPrice: false,
showPlus: false, showPlus: false,
showLBC: true,
}; };
render() { render() {
@ -33,9 +35,10 @@ class CreditAmount extends React.PureComponent<Props> {
showPlus, showPlus,
large, large,
isEstimate, isEstimate,
plain,
noStyle,
fee, fee,
showLBC,
inheritStyle,
filePage,
} = this.props; } = this.props;
const minimumRenderableAmount = 10 ** (-1 * precision); const minimumRenderableAmount = 10 ** (-1 * precision);
@ -62,7 +65,7 @@ class CreditAmount extends React.PureComponent<Props> {
amountText = `+${amountText}`; amountText = `+${amountText}`;
} }
if (!plain) { if (showLBC) {
amountText = `${amountText} ${__('LBC')}`; amountText = `${amountText} ${__('LBC')}`;
} }
@ -78,8 +81,8 @@ class CreditAmount extends React.PureComponent<Props> {
'credit-amount--free': !large && isFree, 'credit-amount--free': !large && isFree,
'credit-amount--cost': !large && !isFree, 'credit-amount--cost': !large && !isFree,
'credit-amount--large': large, 'credit-amount--large': large,
'credit-amount--plain': plain, 'credit-amount--inherit': inheritStyle,
'credit-amount--no-style': noStyle, 'credit-amount--file-page': filePage,
})} })}
> >
{amountText} {amountText}

View file

@ -4,7 +4,7 @@ import ReactDOMServer from 'react-dom/server';
import classnames from 'classnames'; import classnames from 'classnames';
import MarkdownPreview from 'component/common/markdown-preview'; import MarkdownPreview from 'component/common/markdown-preview';
import SimpleMDE from 'react-simplemde-editor'; import SimpleMDE from 'react-simplemde-editor';
import 'simplemde/dist/simplemde.min.css'; import 'simplemde/dist/simplemde.min.css'; // eslint-disable-line import/no-extraneous-dependencies
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import { openEditorMenu } from 'util/contextMenu'; import { openEditorMenu } from 'util/contextMenu';
@ -24,6 +24,7 @@ type Props = {
stretch?: boolean, stretch?: boolean,
affixClass?: string, // class applied to prefix/postfix label affixClass?: string, // class applied to prefix/postfix label
useToggle?: boolean, useToggle?: boolean,
firstInList?: boolean, // at the top of a list, no padding top
}; };
export class FormField extends React.PureComponent<Props> { export class FormField extends React.PureComponent<Props> {
@ -41,6 +42,7 @@ export class FormField extends React.PureComponent<Props> {
stretch, stretch,
affixClass, affixClass,
useToggle, useToggle,
firstInList,
...inputProps ...inputProps
} = this.props; } = this.props;
@ -108,6 +110,7 @@ export class FormField extends React.PureComponent<Props> {
<div <div
className={classnames('form-field__input', { className={classnames('form-field__input', {
'form-field--auto-height': type === 'markdown', 'form-field--auto-height': type === 'markdown',
'form-field--first-item': firstInList,
})} })}
> >
{prefix && ( {prefix && (

View file

@ -92,9 +92,8 @@ class FileCard extends React.PureComponent<Props> {
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
> >
<CardMedia thumbnail={thumbnail} /> <CardMedia thumbnail={thumbnail} />
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
<div className="card__title-identity"> <div className="card__title-identity">
<div className="card__title--small"> <div className="card__title--small card__title--file-card">
<TruncatedText lines={3}>{title}</TruncatedText> <TruncatedText lines={3}>{title}</TruncatedText>
</div> </div>
<div className="card__subtitle"> <div className="card__subtitle">
@ -103,13 +102,12 @@ class FileCard extends React.PureComponent<Props> {
) : ( ) : (
<React.Fragment> <React.Fragment>
<UriIndicator uri={uri} link /> <UriIndicator uri={uri} link />
<div> {isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />} {fileInfo && <Icon icon={icons.LOCAL} />}
{fileInfo && <Icon icon={icons.LOCAL} />}
</div>
</React.Fragment> </React.Fragment>
)} )}
</div> </div>
{showPrice && <FilePrice uri={uri} />}
</div> </div>
</section> </section>
); );

View file

@ -9,6 +9,10 @@ type Props = {
uri: string, uri: string,
fetching: boolean, fetching: boolean,
claim: ?{}, claim: ?{},
// below props are just passed to <CreditAmount />
filePage?: boolean,
inheritStyle?: boolean,
showLBC?: boolean,
}; };
class FilePrice extends React.PureComponent<Props> { class FilePrice extends React.PureComponent<Props> {
@ -33,13 +37,16 @@ class FilePrice extends React.PureComponent<Props> {
}; };
render() { render() {
const { costInfo, showFullPrice } = this.props; const { costInfo, showFullPrice, filePage, inheritStyle, showLBC } = this.props;
return costInfo ? ( return costInfo ? (
<CreditAmount <CreditAmount
showFree
filePage={filePage}
inheritStyle={inheritStyle}
showLBC={showLBC}
amount={costInfo.cost} amount={costInfo.cost}
isEstimate={!costInfo.includesData} isEstimate={!costInfo.includesData}
showFree
showFullPrice={showFullPrice} showFullPrice={showFullPrice}
/> />
) : null; ) : null;
skhameneh commented 2018-07-25 20:47:44 +02:00 (Migrated from github.com)
Review

Minor: Split to multiple lines.

Minor: Split to multiple lines.

View file

@ -11,7 +11,6 @@ import classnames from 'classnames';
import FilePrice from 'component/filePrice'; import FilePrice from 'component/filePrice';
type Props = { type Props = {
fullWidth: boolean, // removes the max-width css
showUri: boolean, showUri: boolean,
showLocal: boolean, showLocal: boolean,
obscureNsfw: boolean, obscureNsfw: boolean,
@ -28,13 +27,15 @@ type Props = {
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
hideNoResult: boolean, // don't show the tile if there is no claim at this uri hideNoResult: boolean, // don't show the tile if there is no claim at this uri
displayHiddenMessage?: boolean, displayHiddenMessage?: boolean,
displayDescription?: boolean,
small?: boolean,
}; };
class FileTile extends React.PureComponent<Props> { class FileTile extends React.PureComponent<Props> {
static defaultProps = { static defaultProps = {
showUri: false, showUri: false,
showLocal: false, showLocal: false,
fullWidth: false, displayDescription: true,
}; };
componentDidMount() { componentDidMount() {
@ -57,13 +58,14 @@ class FileTile extends React.PureComponent<Props> {
showUri, showUri,
obscureNsfw, obscureNsfw,
claimIsMine, claimIsMine,
fullWidth,
showLocal, showLocal,
isDownloaded, isDownloaded,
clearPublish, clearPublish,
updatePublishForm, updatePublishForm,
hideNoResult, hideNoResult,
displayHiddenMessage, displayHiddenMessage,
displayDescription,
small,
} = this.props; } = this.props;
const shouldHide = !claimIsMine && obscureNsfw && metadata && metadata.nsfw; const shouldHide = !claimIsMine && obscureNsfw && metadata && metadata.nsfw;
@ -96,7 +98,7 @@ class FileTile extends React.PureComponent<Props> {
return !name && hideNoResult ? null : ( return !name && hideNoResult ? null : (
<section <section
className={classnames('file-tile card--link', { className={classnames('file-tile card--link', {
'file-tile--fullwidth': fullWidth, 'file-tile--small': small,
})} })}
onClick={onClick} onClick={onClick}
onKeyUp={onClick} onKeyUp={onClick}
@ -108,20 +110,29 @@ class FileTile extends React.PureComponent<Props> {
{isResolvingUri && <div className="card__title--small">{__('Loading...')}</div>} {isResolvingUri && <div className="card__title--small">{__('Loading...')}</div>}
{!isResolvingUri && ( {!isResolvingUri && (
<React.Fragment> <React.Fragment>
<div className="card__title--small card__title--file"> <div
<TruncatedText lines={2}>{title || name}</TruncatedText> className={classnames({
'card__title--file': !small,
'card__title--x-small': small,
})}
>
<TruncatedText lines={3}>{title || name}</TruncatedText>
</div> </div>
<div className="card__subtitle"> <div
className={classnames('card__subtitle', {
'card__subtitle--x-small': small,
})}
>
{showUri ? uri : channel || __('Anonymous')} {showUri ? uri : channel || __('Anonymous')}
{isRewardContent && <Icon icon={icons.FEATURED} />} {isRewardContent && <Icon icon={icons.FEATURED} />}
{showLocal && isDownloaded && <Icon icon={icons.LOCAL} />} {showLocal && isDownloaded && <Icon icon={icons.LOCAL} />}
</div> </div>
<div className="card__subtext card__subtext--small"> <FilePrice uri={uri} />
<TruncatedText lines={3}>{description}</TruncatedText> {displayDescription && (
</div> <div className="card__subtext card__subtext--small">
<div className="card__subtitle-price"> <TruncatedText lines={3}>{description}</TruncatedText>
<FilePrice uri={uri} /> </div>
</div> )}
{!name && ( {!name && (
<React.Fragment> <React.Fragment>
{__('This location is unused.')}{' '} {__('This location is unused.')}{' '}

View file

@ -12,6 +12,7 @@ type Props = {
noPadding: ?boolean, noPadding: ?boolean,
extraPadding: ?boolean, extraPadding: ?boolean,
notContained: ?boolean, // No max-width, but keep the padding notContained: ?boolean, // No max-width, but keep the padding
forContent: ?boolean,
loading: ?boolean, loading: ?boolean,
}; };
@ -71,15 +72,24 @@ class Page extends React.PureComponent<Props, State> {
loaderTimeout: ?TimeoutID; loaderTimeout: ?TimeoutID;
render() { render() {
const { pageTitle, children, noPadding, extraPadding, notContained, loading } = this.props; const {
pageTitle,
children,
noPadding,
extraPadding,
notContained,
loading,
forContent,
} = this.props;
const { showLoader } = this.state; const { showLoader } = this.state;
return ( return (
<main <main
className={classnames('main', { className={classnames('main', {
'main--contained': !notContained && !noPadding && !extraPadding, 'main--contained': !notContained && !noPadding && !extraPadding && !forContent,
'main--no-padding': noPadding, 'main--no-padding': noPadding,
'main--extra-padding': extraPadding, 'main--extra-padding': extraPadding,
'main--for-content': forContent,
})} })}
> >
{pageTitle && ( {pageTitle && (

View file

@ -0,0 +1,22 @@
import * as settings from 'constants/settings';
import { connect } from 'react-redux';
import { doFetchClaimsByChannel } from 'redux/actions/content';
import { makeSelectClaimsInChannelForCurrentPage } from 'lbry-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import RecommendedVideos from './view';
const select = (state, props) => ({
claimsInChannel: makeSelectClaimsInChannelForCurrentPage(props.channelUri)(state),
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
});
const perform = dispatch => ({
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
setAutoplay: value => dispatch(doSetClientSetting(settings.AUTOPLAY, value)),
});
export default connect(
select,
perform
)(RecommendedVideos);

View file

@ -0,0 +1,76 @@
// @flow
import React from 'react';
import FileTile from 'component/fileTile';
import { FormRow, FormField } from 'component/common/form';
import ToolTip from 'component/common/tooltip';
import type { Claim } from 'types/claim';
import { buildURI, parseURI } from 'lbry-redux';
type Props = {
uri: string,
channelUri: ?string,
claimsInChannel: ?Array<Claim>,
autoplay: boolean,
setAutoplay: boolean => void,
fetchClaims: (string, number) => void,
};
export default class RecommendedContent extends React.PureComponent<Props> {
componentDidMount() {
const { channelUri, fetchClaims, claimsInChannel } = this.props;
if (channelUri && !claimsInChannel) {
fetchClaims(channelUri, 1);
}
}
render() {
const { claimsInChannel, autoplay, uri, setAutoplay } = this.props;
let recommendedContent;
if (claimsInChannel) {
recommendedContent = claimsInChannel.filter(claim => {
neb-b commented 2018-07-25 20:16:22 +02:00 (Migrated from github.com)
Review

This will be moved into a selector when I add recommended content from search results (my next PR)

This will be moved into a selector when I add recommended content from search results (my next PR)
skhameneh commented 2018-07-25 20:59:00 +02:00 (Migrated from github.com)
Review

👍

👍
const { name, claim_id: claimId, channel_name: channelName, value } = claim;
const { isChannel } = parseURI(uri);
// The uri may include the channel name
const recommendedUri =
isChannel && value && value.publisherSignature
? buildURI({
contentName: name,
claimName: channelName,
claimId: value.publisherSignature.certificateId,
})
: buildURI({ claimName: name, claimId });
return recommendedUri !== uri;
});
}
return (
<section className="card__list--recommended">
<FormRow>
<ToolTip onComponent body={__('Automatically download and play free content.')}>
<FormField
useToggle
firstInList
name="autoplay"
type="checkbox"
prefix={__('Autoplay')}
checked={autoplay}
onChange={e => setAutoplay(e.target.checked)}
/>
</ToolTip>
</FormRow>
{recommendedContent &&
recommendedContent.map(({ permanent_url: permanentUrl }) => (
<FileTile
small
displayDescription={false}
key={permanentUrl}
uri={`lbry://${permanentUrl}`}
/>
))}
</section>
);
}
}

View file

@ -31,7 +31,7 @@ class RewardSummary extends React.Component<Props> {
<React.Fragment> <React.Fragment>
{__('You have')} {__('You have')}
&nbsp; &nbsp;
<CreditAmount noStyle amount={unclaimedRewardAmount} precision={8} /> <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />
&nbsp; &nbsp;
{__('in unclaimed rewards')}. {__('in unclaimed rewards')}.
</React.Fragment> </React.Fragment>

View file

@ -6,8 +6,8 @@ import DateTime from 'component/dateTime';
import Button from 'component/button'; import Button from 'component/button';
import { buildURI } from 'lbry-redux'; import { buildURI } from 'lbry-redux';
import * as txnTypes from 'constants/transaction_types'; import * as txnTypes from 'constants/transaction_types';
import type { Transaction } from '../view';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import type { Transaction } from '../view';
type Props = { type Props = {
transaction: Transaction, transaction: Transaction,
@ -25,12 +25,6 @@ class TransactionListItem extends React.PureComponent<Props> {
(this: any).abandonClaim = this.abandonClaim.bind(this); (this: any).abandonClaim = this.abandonClaim.bind(this);
} }
abandonClaim() {
const { txid, nout } = this.props.transaction;
this.props.revokeClaim(txid, nout);
}
getLink(type: string) { getLink(type: string) {
if (type === txnTypes.TIP) { if (type === txnTypes.TIP) {
return <Button icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />; return <Button icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />;
@ -38,10 +32,14 @@ class TransactionListItem extends React.PureComponent<Props> {
return <Button icon={ICONS.TRASH} onClick={this.abandonClaim} title={__('Abandon Claim')} />; return <Button icon={ICONS.TRASH} onClick={this.abandonClaim} title={__('Abandon Claim')} />;
} }
capitalize(string: string) { abandonClaim() {
return string.charAt(0).toUpperCase() + string.slice(1); const { txid, nout } = this.props.transaction;
this.props.revokeClaim(txid, nout);
} }
capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1);
render() { render() {
const { reward, transaction, isRevokeable } = this.props; const { reward, transaction, isRevokeable } = this.props;
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction; const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
@ -55,12 +53,12 @@ class TransactionListItem extends React.PureComponent<Props> {
return ( return (
<tr> <tr>
<td> <td>
<CreditAmount amount={amount} plain noStyle showPlus precision={8} /> <CreditAmount inheritStyle showPlus amount={amount} precision={8} />
<br /> <br />
{fee !== 0 && ( {fee !== 0 && (
<span className="table__item-label"> <span className="table__item-label">
<CreditAmount plain noStyle fee amount={fee} precision={8} /> <CreditAmount inheritStyle fee amount={fee} precision={8} />
</span> </span>
)} )}
</td> </td>

View file

@ -18,7 +18,7 @@ export default (props: Props) => {
<Button <Button
icon={icons.GLOBE} icon={icons.GLOBE}
button="alt" button="alt"
label={__('View on Web')} label={__('Share')}
href={`http://spee.ch/${speechURL}`} href={`http://spee.ch/${speechURL}`}
/> />
) : null; ) : null;

View file

@ -31,7 +31,7 @@ class ModalAffirmPurchase extends React.PureComponent {
> >
{__('This will purchase')} <strong>{title}</strong> {__('for')}{' '} {__('This will purchase')} <strong>{title}</strong> {__('for')}{' '}
<strong> <strong>
<FilePrice uri={uri} showFullPrice look="plain" /> <FilePrice uri={uri} showFullPrice inheritStyle showLBC={false} />
</strong>{' '} </strong>{' '}
{__('credits')}. {__('credits')}.
</Modal> </Modal>

View file

@ -14,14 +14,12 @@ import Button from 'component/button';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import ViewOnWebButton from 'component/viewOnWebButton'; import ViewOnWebButton from 'component/viewOnWebButton';
import Page from 'component/page'; import Page from 'component/page';
import * as settings from 'constants/settings';
import type { Claim } from 'types/claim'; import type { Claim } from 'types/claim';
import type { Subscription } from 'types/subscription'; import type { Subscription } from 'types/subscription';
import FileDownloadLink from 'component/fileDownloadLink'; import FileDownloadLink from 'component/fileDownloadLink';
import classnames from 'classnames'; import classnames from 'classnames';
import { FormField, FormRow } from 'component/common/form';
import ToolTip from 'component/common/tooltip';
import getMediaType from 'util/getMediaType'; import getMediaType from 'util/getMediaType';
import RecommendedContent from 'component/recommendedContent';
type Props = { type Props = {
claim: Claim, claim: Claim,
@ -37,14 +35,12 @@ type Props = {
rewardedContentClaimIds: Array<string>, rewardedContentClaimIds: Array<string>,
obscureNsfw: boolean, obscureNsfw: boolean,
claimIsMine: boolean, claimIsMine: boolean,
autoplay: boolean,
costInfo: ?{}, costInfo: ?{},
navigate: (string, ?{}) => void, navigate: (string, ?{}) => void,
openModal: ({ id: string }, { uri: string }) => void, openModal: ({ id: string }, { uri: string }) => void,
fetchFileInfo: string => void, fetchFileInfo: string => void,
fetchCostInfo: string => void, fetchCostInfo: string => void,
prepareEdit: ({}, string) => void, prepareEdit: ({}, string) => void,
setClientSetting: (string, boolean | string) => void,
checkSubscription: ({ channelName: string, uri: string }) => void, checkSubscription: ({ channelName: string, uri: string }) => void,
subscriptions: Array<Subscription>, subscriptions: Array<Subscription>,
}; };
@ -62,12 +58,6 @@ class FilePage extends React.Component<Props> {
'application', 'application',
]; ];
constructor(props: Props) {
super(props);
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
}
componentDidMount() { componentDidMount() {
const { uri, fileInfo, fetchFileInfo, fetchCostInfo } = this.props; const { uri, fileInfo, fetchFileInfo, fetchCostInfo } = this.props;
@ -88,10 +78,6 @@ class FilePage extends React.Component<Props> {
} }
} }
onAutoplayChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
}
checkSubscription = (props: Props) => { checkSubscription = (props: Props) => {
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) { if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
props.checkSubscription({ props.checkSubscription({
@ -119,7 +105,6 @@ class FilePage extends React.Component<Props> {
claimIsMine, claimIsMine,
prepareEdit, prepareEdit,
navigate, navigate,
autoplay,
costInfo, costInfo,
fileInfo, fileInfo,
} = this.props; } = this.props;
@ -160,99 +145,79 @@ class FilePage extends React.Component<Props> {
} }
return ( return (
<Page extraPadding> <Page forContent>
{!claim || !metadata ? ( <section className="content__wrapper">
<section> {showFile && <FileViewer className="content__embedded" uri={uri} mediaType={mediaType} />}
<span className="empty">{__('Empty claim or metadata info.')}</span> {!showFile &&
</section> (thumbnail ? (
) : ( <Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
<section className="card"> ) : (
{showFile && ( <div
<FileViewer className="content__embedded" uri={uri} mediaType={mediaType} /> className={classnames('content__empty', {
)} 'content__empty--nsfw': shouldObscureThumbnail,
{!showFile && })}
(thumbnail ? ( >
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} /> <div className="card__media-text">
) : ( {__("Sorry, looks like we can't preview this file.")}
<div
className={classnames('content__empty', {
'content__empty--nsfw': shouldObscureThumbnail,
})}
>
<div className="card__media-text">
{__("Sorry, looks like we can't preview this file.")}
</div>
</div>
))}
<div className="card__content">
<div className="card__title-identity--file">
<h1 className="card__title card__title--file">{title}</h1>
<div className="card__title-identity-icons">
{isRewardContent && (
<Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
)}
<FilePrice uri={normalizeURI(uri)} />
</div> </div>
</div> </div>
<span className="card__subtitle card__subtitle--file"> ))}
{__('Published on')}&nbsp;
<DateTime block={height} show={DateTime.SHOW_DATE} />
</span>
{metadata.nsfw && <div>NSFW</div>}
<div className="card__channel-info">
<UriIndicator uri={uri} link />
</div>
<div className="card__actions card__actions--no-margin card__actions--between">
<div className="card__actions">
{claimIsMine ? (
<Button
button="primary"
icon={icons.EDIT}
label={__('Edit')}
onClick={() => {
prepareEdit(claim, editUri);
navigate('/publish');
}}
/>
) : (
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
)}
{!claimIsMine && (
<Button
button="alt"
icon={icons.GIFT}
label={__('Enjoy this? Send a tip')}
onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })}
/>
)}
{speechSharable && (
<ViewOnWebButton claimId={claim.claim_id} claimName={claim.name} />
)}
</div>
<div className="card__actions"> <div className="card__content">
<FileDownloadLink uri={uri} /> <div className="card__title-identity--file">
<FileActions uri={uri} claimId={claim.claim_id} /> <h1 className="card__title card__title--file">{title}</h1>
</div> <div className="card__title-identity-icons">
{isRewardContent && <Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} />}
<FilePrice filePage uri={normalizeURI(uri)} />
</div> </div>
<FormRow padded> </div>
<ToolTip onComponent body={__('Automatically download and play free content.')}> <span className="card__subtitle card__subtitle--file">
<FormField {__('Published on')}&nbsp;
useToggle <DateTime block={height} show={DateTime.SHOW_DATE} />
name="autoplay" </span>
type="checkbox" {metadata.nsfw && <div>NSFW</div>}
postfix={__('Autoplay')} <div className="card__channel-info">
checked={autoplay} <UriIndicator uri={uri} link />
onChange={this.onAutoplayChange} </div>
<div className="card__actions card__actions--no-margin card__actions--between">
<div className="card__actions">
{claimIsMine ? (
<Button
button="primary"
icon={icons.EDIT}
label={__('Edit')}
onClick={() => {
prepareEdit(claim, editUri);
navigate('/publish');
}}
/> />
</ToolTip> ) : (
</FormRow> <SubscribeButton uri={subscriptionUri} channelName={channelName} />
)}
{!claimIsMine && (
<Button
button="alt"
icon={icons.GIFT}
label={__('Send a tip')}
onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })}
/>
)}
{speechSharable && (
<ViewOnWebButton claimId={claim.claim_id} claimName={claim.name} />
)}
</div>
<div className="card__actions">
<FileDownloadLink uri={uri} />
<FileActions uri={uri} claimId={claim.claim_id} />
</div>
</div> </div>
<div className="card__content--extra-padding"> <div className="card__content--extra-padding">
<FileDetails uri={uri} /> <FileDetails uri={uri} />
</div> </div>
</section> </div>
)} </section>
<RecommendedContent uri={uri} channelUri={`lbry://${subscriptionUri}`} />
</Page> </Page>
); );
} }

View file

@ -73,7 +73,7 @@ class SearchPage extends React.PureComponent<Props> {
<Icon icon={icons.HELP} /> <Icon icon={icons.HELP} />
</ToolTip> </ToolTip>
</div> </div>
<FileTile fullWidth showUri displayHiddenMessage uri={normalizeURI(query)} /> <FileTile showUri displayHiddenMessage uri={normalizeURI(query)} />
</React.Fragment> </React.Fragment>
)} )}
<FileListSearch query={query} /> <FileListSearch query={query} />

View file

@ -203,6 +203,12 @@ p {
padding-right: 100px; padding-right: 100px;
} }
.main--for-content {
padding: $spacing-width * 2/3;
display: flex;
justify-content: center;
}
.page__header { .page__header {
padding: $spacing-vertical * 2/3; padding: $spacing-vertical * 2/3;
padding-bottom: 0; padding-bottom: 0;
@ -250,11 +256,10 @@ p {
} }
.credit-amount { .credit-amount {
border-radius: 5px;
font-family: 'metropolis-bold'; font-family: 'metropolis-bold';
font-size: 10px; font-size: 10px;
padding: 5px;
white-space: nowrap; white-space: nowrap;
padding: $spacing-vertical * 1/6 0;
} }
.credit-amount--large { .credit-amount--large {
@ -262,30 +267,36 @@ p {
font-size: 36px; font-size: 36px;
} }
.credit-amount--file-page {
border-radius: 5px;
padding: 5px;
}
.credit-amount--free { .credit-amount--free {
color: var(--color-dark-blue); color: var(--color-secondary);
background-color: var(--color-secondary);
&.credit-amount--file-page {
color: var(--color-dark-blue);
background-color: var(--color-secondary);
}
} }
.credit-amount--cost { .credit-amount--cost {
color: var(--color-black); color: var(--color-yellow);
background-color: var(--color-yellow);
&.credit-amount--file-page {
color: var(--color-black);
background-color: var(--color-yellow);
}
} }
.credit-amount--plain { .credit-amount--inherit {
background-color: inherit; background-color: inherit;
color: inherit; color: inherit;
font-weight: inherit; font-weight: inherit;
font-size: inherit; font-size: inherit;
}
.credit-amount.credit-amount--no-style {
padding: 0;
font-size: inherit;
font-weight: inherit;
color: inherit;
background-color: transparent;
font-family: 'metropolis-medium'; font-family: 'metropolis-medium';
padding: 0;
} }
.divider__horizontal { .divider__horizontal {

View file

@ -9,7 +9,7 @@ $large-breakpoint: 1921px;
:root { :root {
/* Widths & spacings */ /* Widths & spacings */
--side-nav-width: 220px; --side-nav-width: 190px;
--side-nav-width-m: 240px; --side-nav-width-m: 240px;
--side-nav-width-l: 320px; --side-nav-width-l: 320px;
--font-size-subtext-multiple: 0.92; --font-size-subtext-multiple: 0.92;
@ -153,9 +153,14 @@ $large-breakpoint: 1921px;
--success-msg-border: var(--color-green-blue); --success-msg-border: var(--color-green-blue);
--success-msg-bg: var(--color-green-light); --success-msg-bg: var(--color-green-light);
/* File Tile Card */ /* File */
--file-tile--media-height: 125px; --file-tile-media-height: 125px;
--file-tile--media-width: calc(125px * (16 / 9)); --file-tile-media-width: calc(125px * (16 / 9));
--file-tile-media-height-small: 60px;
--file-tile-media-width-small: calc(60px * (16 / 9));
--file-page-min-width: 400px;
--recommended-content-width: 300px;
--recommended-content-width-medium: 400px;
/* Modal */ /* Modal */
--modal-width: 440px; --modal-width: 440px;

View file

@ -38,7 +38,6 @@
@media only screen and (min-width: $medium-breakpoint) { @media only screen and (min-width: $medium-breakpoint) {
font-size: 14px; font-size: 14px;
padding-top: 4px;
} }
} }
} }
@ -114,13 +113,17 @@
.card__title--small { .card__title--small {
font-size: 14px; font-size: 14px;
line-height: 18px; line-height: 18px;
padding-top: $spacing-vertical / 3;
@media only screen and (min-width: $large-breakpoint) { @media only screen and (min-width: $large-breakpoint) {
font-size: 16px; font-size: 16px;
} }
} }
.card__title--x-small {
font-size: 12px;
line-height: 12px;
}
.card__title--file { .card__title--file {
font-family: 'metropolis-bold'; font-family: 'metropolis-bold';
font-size: 28px; font-size: 28px;
@ -130,25 +133,36 @@
font-size: 18px; font-size: 18px;
} }
.card__title--file-card {
padding-top: $spacing-vertical * 1/3;
}
.card__subtitle { .card__subtitle {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
font-family: 'metropolis-medium'; font-family: 'metropolis-medium';
color: var(--card-text-color); color: var(--card-text-color);
display: flex;
align-items: center;
.icon { .icon {
margin-top: $spacing-vertical * 1/6; margin: 0 0 0 $spacing-vertical * 1/3;
&:not(:first-of-type) {
margin: 0 $spacing-vertical * 1/3;
}
} }
} }
.card__subtitle--x-small {
font-size: 12px;
}
.card__subtitle-price { .card__subtitle-price {
padding-top: $spacing-vertical * 1/3; padding-top: $spacing-vertical * 1/3;
} }
.card__title--small + .card__subtitle,
.card__title--x-small + .card__subtitle {
padding-top: $spacing-vertical * 1/3;
}
.card__meta { .card__meta {
color: var(--color-help); color: var(--color-help);
font-size: 14px; font-size: 14px;
@ -312,7 +326,11 @@
.card-row__scroll-btns { .card-row__scroll-btns {
display: flex; display: flex;
padding-right: $spacing-width; padding-right: $spacing-width * 1/3;
@media (min-width: $medium-breakpoint) {
padding-right: $spacing-width;
}
} }
.card-row__scrollhouse { .card-row__scrollhouse {
@ -401,6 +419,15 @@
} }
} }
.card__list--recommended {
flex: 0 0 var(--recommended-content-width);
padding-left: $spacing-width;
@media (min-width: $medium-breakpoint) {
flex: 0 0 var(--recommended-content-width-medium);
}
}
.card__success-msg { .card__success-msg {
border-left: 2px solid var(--success-msg-border); border-left: 2px solid var(--success-msg-border);
color: var(--success-msg-color); color: var(--success-msg-color);

View file

@ -1,3 +1,8 @@
.content__wrapper {
max-width: var(--card-max-width);
flex: 1 0 var(--file-page-min-width);
}
.content__embedded { .content__embedded {
background-color: var(--color-black); background-color: var(--color-black);
width: 100%; width: 100%;

View file

@ -22,11 +22,11 @@
.file-tile { .file-tile {
display: flex; display: flex;
margin-top: $spacing-vertical; padding-top: $spacing-vertical;
.card__media { .card__media {
height: var(--file-tile--media-height); height: var(--file-tile-media-height);
flex: 0 0 var(--file-tile--media-width); flex: 0 0 var(--file-tile-media-width);
} }
.card__subtitle { .card__subtitle {
@ -34,8 +34,13 @@
} }
} }
.file-tile--fullwidth { .file-tile.file-tile--small {
max-width: none; padding-top: $spacing-vertical * 2/3;
.card__media {
height: var(--file-tile-media-height-small);
flex: 0 0 var(--file-tile-media-width-small);
}
} }
.file-tile__info { .file-tile__info {

View file

@ -51,6 +51,10 @@
width: 100%; width: 100%;
} }
.form-field__input.form-field--first-item {
padding: 0;
}
.form-field__input { .form-field__input {
display: flex; display: flex;
padding-top: $spacing-vertical / 3; padding-top: $spacing-vertical / 3;

View file

@ -6,9 +6,13 @@
z-index: 1; z-index: 1;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 $spacing-width; padding: 0 $spacing-width * 1/3;
background-color: var(--color-bg); background-color: var(--color-bg);
box-shadow: var(--box-shadow-header); box-shadow: var(--box-shadow-header);
@media (min-width: $medium-breakpoint) {
padding: 0 $spacing-width;
}
} }
.header__navigation { .header__navigation {

View file

@ -1,7 +1,7 @@
.nav { .nav {
width: var(--side-nav-width); width: var(--side-nav-width);
background-color: var(--nav-bg-color); background-color: var(--nav-bg-color);
padding: $spacing-width; padding: $spacing-width * 1/3;
color: var(--nav-color); color: var(--nav-color);
hr { hr {
@ -11,8 +11,13 @@
margin: $spacing-vertical $spacing-vertical * 2/3; margin: $spacing-vertical $spacing-vertical * 2/3;
} }
@media (min-width: $medium-breakpoint) {
padding-left: $spacing-width;
width: calc(var(--side-nav-width) * 1.2);
}
@media (min-width: $large-breakpoint) { @media (min-width: $large-breakpoint) {
width: calc(var(--side-nav-width) * 1.1); width: calc(var(--side-nav-width) * 1.4);
} }
} }