Merge branch 'master' into fix-viewer
This commit is contained in:
commit
605136ac64
33 changed files with 394 additions and 200 deletions
|
@ -38,6 +38,7 @@
|
|||
"import/prefer-default-export": 0,
|
||||
"no-return-assign": 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
2
.gitignore
vendored
|
@ -5,4 +5,4 @@
|
|||
yarn-error.log
|
||||
package-lock.json
|
||||
.idea/
|
||||
/build/daemon.ver
|
||||
/build/daemon*
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "LBRY",
|
||||
"version": "0.23.0",
|
||||
"version": "0.23.1",
|
||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||
"keywords": [
|
||||
"lbry"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { app, BrowserWindow, dialog, screen } from 'electron';
|
||||
import { app, BrowserWindow, dialog, shell, screen } from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
|
||||
|
@ -124,5 +124,10 @@ export default appState => {
|
|||
window = null;
|
||||
});
|
||||
|
||||
window.webContents.on('new-window', (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
||||
|
|
|
@ -11,9 +11,10 @@ type Props = {
|
|||
showPlus: boolean,
|
||||
isEstimate?: boolean,
|
||||
large?: boolean,
|
||||
plain?: boolean,
|
||||
showLBC?: boolean,
|
||||
fee?: boolean,
|
||||
noStyle?: boolean,
|
||||
inheritStyle?: boolean,
|
||||
filePage?: boolean,
|
||||
};
|
||||
|
||||
class CreditAmount extends React.PureComponent<Props> {
|
||||
|
@ -22,6 +23,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
showFree: false,
|
||||
showFullPrice: false,
|
||||
showPlus: false,
|
||||
showLBC: true,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -33,9 +35,10 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
showPlus,
|
||||
large,
|
||||
isEstimate,
|
||||
plain,
|
||||
noStyle,
|
||||
fee,
|
||||
showLBC,
|
||||
inheritStyle,
|
||||
filePage,
|
||||
} = this.props;
|
||||
|
||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||
|
@ -62,7 +65,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
amountText = `+${amountText}`;
|
||||
}
|
||||
|
||||
if (!plain) {
|
||||
if (showLBC) {
|
||||
amountText = `${amountText} ${__('LBC')}`;
|
||||
}
|
||||
|
||||
|
@ -78,8 +81,8 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
'credit-amount--free': !large && isFree,
|
||||
'credit-amount--cost': !large && !isFree,
|
||||
'credit-amount--large': large,
|
||||
'credit-amount--plain': plain,
|
||||
'credit-amount--no-style': noStyle,
|
||||
'credit-amount--inherit': inheritStyle,
|
||||
'credit-amount--file-page': filePage,
|
||||
})}
|
||||
>
|
||||
{amountText}
|
||||
|
|
|
@ -4,7 +4,7 @@ import ReactDOMServer from 'react-dom/server';
|
|||
import classnames from 'classnames';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
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 { openEditorMenu, stopContextMenu } from 'util/contextMenu';
|
||||
|
||||
|
@ -24,6 +24,7 @@ type Props = {
|
|||
stretch?: boolean,
|
||||
affixClass?: string, // class applied to prefix/postfix label
|
||||
useToggle?: boolean,
|
||||
firstInList?: boolean, // at the top of a list, no padding top
|
||||
};
|
||||
|
||||
export class FormField extends React.PureComponent<Props> {
|
||||
|
@ -41,6 +42,7 @@ export class FormField extends React.PureComponent<Props> {
|
|||
stretch,
|
||||
affixClass,
|
||||
useToggle,
|
||||
firstInList,
|
||||
...inputProps
|
||||
} = this.props;
|
||||
|
||||
|
@ -103,6 +105,7 @@ export class FormField extends React.PureComponent<Props> {
|
|||
<div
|
||||
className={classnames('form-field__input', {
|
||||
'form-field--auto-height': type === 'markdown',
|
||||
'form-field--first-item': firstInList,
|
||||
})}
|
||||
>
|
||||
{prefix && (
|
||||
|
|
|
@ -92,9 +92,8 @@ class FileCard extends React.PureComponent<Props> {
|
|||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<CardMedia thumbnail={thumbnail} />
|
||||
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
|
||||
<div className="card__title-identity">
|
||||
<div className="card__title--small">
|
||||
<div className="card__title--small card__title--file-card">
|
||||
<TruncatedText lines={3}>{title}</TruncatedText>
|
||||
</div>
|
||||
<div className="card__subtitle">
|
||||
|
@ -103,13 +102,12 @@ class FileCard extends React.PureComponent<Props> {
|
|||
) : (
|
||||
<React.Fragment>
|
||||
<UriIndicator uri={uri} link />
|
||||
<div>
|
||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||
</div>
|
||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
{showPrice && <FilePrice uri={uri} />}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,10 @@ type Props = {
|
|||
uri: string,
|
||||
fetching: boolean,
|
||||
claim: ?{},
|
||||
// below props are just passed to <CreditAmount />
|
||||
filePage?: boolean,
|
||||
inheritStyle?: boolean,
|
||||
showLBC?: boolean,
|
||||
};
|
||||
|
||||
class FilePrice extends React.PureComponent<Props> {
|
||||
|
@ -33,13 +37,16 @@ class FilePrice extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { costInfo, showFullPrice } = this.props;
|
||||
const { costInfo, showFullPrice, filePage, inheritStyle, showLBC } = this.props;
|
||||
|
||||
return costInfo ? (
|
||||
<CreditAmount
|
||||
showFree
|
||||
filePage={filePage}
|
||||
inheritStyle={inheritStyle}
|
||||
showLBC={showLBC}
|
||||
amount={costInfo.cost}
|
||||
isEstimate={!costInfo.includesData}
|
||||
showFree
|
||||
showFullPrice={showFullPrice}
|
||||
/>
|
||||
) : null;
|
||||
|
|
|
@ -11,7 +11,6 @@ import classnames from 'classnames';
|
|||
import FilePrice from 'component/filePrice';
|
||||
|
||||
type Props = {
|
||||
fullWidth: boolean, // removes the max-width css
|
||||
showUri: boolean,
|
||||
showLocal: boolean,
|
||||
obscureNsfw: boolean,
|
||||
|
@ -28,13 +27,15 @@ type Props = {
|
|||
updatePublishForm: ({}) => void,
|
||||
hideNoResult: boolean, // don't show the tile if there is no claim at this uri
|
||||
displayHiddenMessage?: boolean,
|
||||
displayDescription?: boolean,
|
||||
small?: boolean,
|
||||
};
|
||||
|
||||
class FileTile extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
showUri: false,
|
||||
showLocal: false,
|
||||
fullWidth: false,
|
||||
displayDescription: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -57,13 +58,14 @@ class FileTile extends React.PureComponent<Props> {
|
|||
showUri,
|
||||
obscureNsfw,
|
||||
claimIsMine,
|
||||
fullWidth,
|
||||
showLocal,
|
||||
isDownloaded,
|
||||
clearPublish,
|
||||
updatePublishForm,
|
||||
hideNoResult,
|
||||
displayHiddenMessage,
|
||||
displayDescription,
|
||||
small,
|
||||
} = this.props;
|
||||
|
||||
const shouldHide = !claimIsMine && obscureNsfw && metadata && metadata.nsfw;
|
||||
|
@ -96,7 +98,7 @@ class FileTile extends React.PureComponent<Props> {
|
|||
return !name && hideNoResult ? null : (
|
||||
<section
|
||||
className={classnames('file-tile card--link', {
|
||||
'file-tile--fullwidth': fullWidth,
|
||||
'file-tile--small': small,
|
||||
})}
|
||||
onClick={onClick}
|
||||
onKeyUp={onClick}
|
||||
|
@ -108,20 +110,29 @@ class FileTile extends React.PureComponent<Props> {
|
|||
{isResolvingUri && <div className="card__title--small">{__('Loading...')}</div>}
|
||||
{!isResolvingUri && (
|
||||
<React.Fragment>
|
||||
<div className="card__title--small card__title--file">
|
||||
<TruncatedText lines={2}>{title || name}</TruncatedText>
|
||||
<div
|
||||
className={classnames({
|
||||
'card__title--file': !small,
|
||||
'card__title--x-small': small,
|
||||
})}
|
||||
>
|
||||
<TruncatedText lines={3}>{title || name}</TruncatedText>
|
||||
</div>
|
||||
<div className="card__subtitle">
|
||||
<div
|
||||
className={classnames('card__subtitle', {
|
||||
'card__subtitle--x-small': small,
|
||||
})}
|
||||
>
|
||||
{showUri ? uri : channel || __('Anonymous')}
|
||||
{isRewardContent && <Icon icon={icons.FEATURED} />}
|
||||
{showLocal && isDownloaded && <Icon icon={icons.LOCAL} />}
|
||||
</div>
|
||||
<div className="card__subtext card__subtext--small">
|
||||
<TruncatedText lines={3}>{description}</TruncatedText>
|
||||
</div>
|
||||
<div className="card__subtitle-price">
|
||||
<FilePrice uri={uri} />
|
||||
</div>
|
||||
<FilePrice uri={uri} />
|
||||
{displayDescription && (
|
||||
<div className="card__subtext card__subtext--small">
|
||||
<TruncatedText lines={3}>{description}</TruncatedText>
|
||||
</div>
|
||||
)}
|
||||
{!name && (
|
||||
<React.Fragment>
|
||||
{__('This location is unused.')}{' '}
|
||||
|
|
|
@ -12,6 +12,7 @@ type Props = {
|
|||
noPadding: ?boolean,
|
||||
extraPadding: ?boolean,
|
||||
notContained: ?boolean, // No max-width, but keep the padding
|
||||
forContent: ?boolean,
|
||||
loading: ?boolean,
|
||||
};
|
||||
|
||||
|
@ -71,15 +72,24 @@ class Page extends React.PureComponent<Props, State> {
|
|||
loaderTimeout: ?TimeoutID;
|
||||
|
||||
render() {
|
||||
const { pageTitle, children, noPadding, extraPadding, notContained, loading } = this.props;
|
||||
const {
|
||||
pageTitle,
|
||||
children,
|
||||
noPadding,
|
||||
extraPadding,
|
||||
notContained,
|
||||
loading,
|
||||
forContent,
|
||||
} = this.props;
|
||||
const { showLoader } = this.state;
|
||||
|
||||
return (
|
||||
<main
|
||||
className={classnames('main', {
|
||||
'main--contained': !notContained && !noPadding && !extraPadding,
|
||||
'main--contained': !notContained && !noPadding && !extraPadding && !forContent,
|
||||
'main--no-padding': noPadding,
|
||||
'main--extra-padding': extraPadding,
|
||||
'main--for-content': forContent,
|
||||
})}
|
||||
>
|
||||
{pageTitle && (
|
||||
|
|
|
@ -53,7 +53,7 @@ type Props = {
|
|||
clearPublish: () => void,
|
||||
resolveUri: string => void,
|
||||
scrollToTop: () => void,
|
||||
prepareEdit: ({}) => void,
|
||||
prepareEdit: ({ }) => void,
|
||||
resetThumbnailStatus: () => void,
|
||||
};
|
||||
|
||||
|
@ -345,6 +345,13 @@ class PublishForm extends React.PureComponent<Props> {
|
|||
<div className="card__title">{__('Content')}</div>
|
||||
<div className="card__subtitle">
|
||||
{isStillEditing ? __('Editing a claim') : __('What are you publishing?')}
|
||||
{' '}{__(
|
||||
'Read our'
|
||||
)}{' '}
|
||||
<Button button="link" label={__('FAQ')} href="https://lbry.io/faq/how-to-publish" />{' '}
|
||||
{__(
|
||||
'to learn more.'
|
||||
)}
|
||||
</div>
|
||||
{(filePath || !!editingURI) && (
|
||||
<div className="card-media__internal-links">
|
||||
|
@ -402,12 +409,12 @@ class PublishForm extends React.PureComponent<Props> {
|
|||
{uploadThumbnailStatus === THUMBNAIL_STATUSES.API_DOWN ? (
|
||||
__('Enter a URL for your thumbnail.')
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '}
|
||||
<Button button="link" label={__('spee.ch')} href="https://spee.ch/about" />.{' '}
|
||||
{__('Recommended size: 800x450 (16:9)')}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<React.Fragment>
|
||||
{__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '}
|
||||
<Button button="link" label={__('spee.ch')} href="https://spee.ch/about" />.{' '}
|
||||
{__('Recommended size: 800x450 (16:9)')}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
<SelectThumbnail
|
||||
thumbnailPath={thumbnailPath}
|
||||
|
@ -482,7 +489,7 @@ class PublishForm extends React.PureComponent<Props> {
|
|||
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW
|
||||
? ''
|
||||
: `${channel}/`
|
||||
}`}
|
||||
}`}
|
||||
type="text"
|
||||
name="content_name"
|
||||
placeholder="myname"
|
||||
|
|
22
src/renderer/component/recommendedContent/index.js
Normal file
22
src/renderer/component/recommendedContent/index.js
Normal 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);
|
76
src/renderer/component/recommendedContent/view.jsx
Normal file
76
src/renderer/component/recommendedContent/view.jsx
Normal 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 => {
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ class RewardSummary extends React.Component<Props> {
|
|||
<React.Fragment>
|
||||
{__('You have')}
|
||||
|
||||
<CreditAmount noStyle amount={unclaimedRewardAmount} precision={8} />
|
||||
<CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />
|
||||
|
||||
{__('in unclaimed rewards')}.
|
||||
</React.Fragment>
|
||||
|
|
|
@ -18,6 +18,7 @@ type Props = {
|
|||
|
||||
type State = {
|
||||
thumbnailError: boolean,
|
||||
thumbnailErrorImage: string,
|
||||
};
|
||||
|
||||
class SelectThumbnail extends React.PureComponent<Props, State> {
|
||||
|
@ -26,6 +27,7 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
|||
|
||||
this.state = {
|
||||
thumbnailError: false,
|
||||
thumbnailErrorImage: 'no-thumbnail.png',
|
||||
};
|
||||
|
||||
(this: any).handleThumbnailChange = this.handleThumbnailChange.bind(this);
|
||||
|
@ -36,7 +38,7 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
|||
const newThumbnail = e.target.value.replace(' ', '');
|
||||
|
||||
updatePublishForm({ thumbnail: newThumbnail });
|
||||
this.setState({ thumbnailError: false });
|
||||
this.setState({ thumbnailError: false, thumbnailErrorImage: 'no-thumbnail.png' });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -49,9 +51,9 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
|||
thumbnailPath,
|
||||
resetThumbnailStatus,
|
||||
} = this.props;
|
||||
const { thumbnailError } = this.state;
|
||||
const { thumbnailError, thumbnailErrorImage } = this.state;
|
||||
const thumbnailSrc =
|
||||
!thumbnail || thumbnailError ? Native.imagePath('no-thumbnail.png') : thumbnail;
|
||||
!thumbnail || thumbnailError ? Native.imagePath(thumbnailErrorImage) : thumbnail;
|
||||
|
||||
return (
|
||||
<div className="card__content">
|
||||
|
@ -62,7 +64,11 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
|||
className="column__item thumbnail-preview"
|
||||
alt={__('Thumbnail Preview')}
|
||||
onError={() => {
|
||||
this.setState({ thumbnailError: true });
|
||||
this.setState({
|
||||
thumbnailError: true,
|
||||
thumbnailErrorImage:
|
||||
thumbnail && thumbnail.length > 0 ? 'broken.png' : 'no-thumbnail.png',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="column__item">
|
||||
|
|
|
@ -6,8 +6,8 @@ import DateTime from 'component/dateTime';
|
|||
import Button from 'component/button';
|
||||
import { buildURI } from 'lbry-redux';
|
||||
import * as txnTypes from 'constants/transaction_types';
|
||||
import type { Transaction } from '../view';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import type { Transaction } from '../view';
|
||||
|
||||
type Props = {
|
||||
transaction: Transaction,
|
||||
|
@ -25,12 +25,6 @@ class TransactionListItem extends React.PureComponent<Props> {
|
|||
(this: any).abandonClaim = this.abandonClaim.bind(this);
|
||||
}
|
||||
|
||||
abandonClaim() {
|
||||
const { txid, nout } = this.props.transaction;
|
||||
|
||||
this.props.revokeClaim(txid, nout);
|
||||
}
|
||||
|
||||
getLink(type: string) {
|
||||
if (type === txnTypes.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')} />;
|
||||
}
|
||||
|
||||
capitalize(string: string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
abandonClaim() {
|
||||
const { txid, nout } = this.props.transaction;
|
||||
|
||||
this.props.revokeClaim(txid, nout);
|
||||
}
|
||||
|
||||
capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1);
|
||||
|
||||
render() {
|
||||
const { reward, transaction, isRevokeable } = this.props;
|
||||
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
||||
|
@ -55,12 +53,12 @@ class TransactionListItem extends React.PureComponent<Props> {
|
|||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<CreditAmount amount={amount} plain noStyle showPlus precision={8} />
|
||||
<CreditAmount inheritStyle showPlus amount={amount} precision={8} />
|
||||
<br />
|
||||
|
||||
{fee !== 0 && (
|
||||
<span className="table__item-label">
|
||||
<CreditAmount plain noStyle fee amount={fee} precision={8} />
|
||||
<CreditAmount inheritStyle fee amount={fee} precision={8} />
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
|
|
@ -18,7 +18,7 @@ export default (props: Props) => {
|
|||
<Button
|
||||
icon={icons.GLOBE}
|
||||
button="alt"
|
||||
label={__('View on Web')}
|
||||
label={__('Share')}
|
||||
href={`http://spee.ch/${speechURL}`}
|
||||
/>
|
||||
) : null;
|
||||
|
|
|
@ -31,7 +31,7 @@ class ModalAffirmPurchase extends React.PureComponent {
|
|||
>
|
||||
{__('This will purchase')} <strong>{title}</strong> {__('for')}{' '}
|
||||
<strong>
|
||||
<FilePrice uri={uri} showFullPrice look="plain" />
|
||||
<FilePrice uri={uri} showFullPrice inheritStyle showLBC={false} />
|
||||
</strong>{' '}
|
||||
{__('credits')}.
|
||||
</Modal>
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
|
||||
class ModalAutoUpdateDownloaded extends React.PureComponent {
|
||||
type Props = {
|
||||
closeModal: any => any,
|
||||
declineAutoUpdate: () => any,
|
||||
};
|
||||
|
||||
class ModalAutoUpdateDownloaded extends React.PureComponent<Props> {
|
||||
constructor(props: ModalProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { closeModal, declineAutoUpdate } = this.props;
|
||||
|
||||
|
@ -14,7 +28,9 @@ class ModalAutoUpdateDownloaded extends React.PureComponent {
|
|||
contentLabel={__('Update Downloaded')}
|
||||
confirmButtonLabel={__('Use it Now')}
|
||||
abortButtonLabel={__('Upgrade on Close')}
|
||||
confirmButtonDisabled={this.state.disabled}
|
||||
onConfirmed={() => {
|
||||
this.setState({ disabled: true });
|
||||
ipcRenderer.send('autoUpdateAccepted');
|
||||
}}
|
||||
onAborted={() => {
|
||||
|
|
|
@ -14,14 +14,12 @@ import Button from 'component/button';
|
|||
import SubscribeButton from 'component/subscribeButton';
|
||||
import ViewOnWebButton from 'component/viewOnWebButton';
|
||||
import Page from 'component/page';
|
||||
import * as settings from 'constants/settings';
|
||||
import type { Claim } from 'types/claim';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
import FileDownloadLink from 'component/fileDownloadLink';
|
||||
import classnames from 'classnames';
|
||||
import { FormField, FormRow } from 'component/common/form';
|
||||
import ToolTip from 'component/common/tooltip';
|
||||
import getMediaType from 'util/getMediaType';
|
||||
import RecommendedContent from 'component/recommendedContent';
|
||||
|
||||
type Props = {
|
||||
claim: Claim,
|
||||
|
@ -37,14 +35,12 @@ type Props = {
|
|||
rewardedContentClaimIds: Array<string>,
|
||||
obscureNsfw: boolean,
|
||||
claimIsMine: boolean,
|
||||
autoplay: boolean,
|
||||
costInfo: ?{},
|
||||
navigate: (string, ?{}) => void,
|
||||
openModal: ({ id: string }, { uri: string }) => void,
|
||||
fetchFileInfo: string => void,
|
||||
fetchCostInfo: string => void,
|
||||
prepareEdit: ({}, string) => void,
|
||||
setClientSetting: (string, boolean | string) => void,
|
||||
checkSubscription: ({ channelName: string, uri: string }) => void,
|
||||
subscriptions: Array<Subscription>,
|
||||
};
|
||||
|
@ -63,12 +59,6 @@ class FilePage extends React.Component<Props> {
|
|||
'application',
|
||||
];
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { uri, fileInfo, fetchFileInfo, fetchCostInfo } = this.props;
|
||||
|
||||
|
@ -89,10 +79,6 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||
}
|
||||
|
||||
checkSubscription = (props: Props) => {
|
||||
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
||||
props.checkSubscription({
|
||||
|
@ -120,7 +106,6 @@ class FilePage extends React.Component<Props> {
|
|||
claimIsMine,
|
||||
prepareEdit,
|
||||
navigate,
|
||||
autoplay,
|
||||
costInfo,
|
||||
fileInfo,
|
||||
} = this.props;
|
||||
|
@ -161,99 +146,79 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page extraPadding>
|
||||
{!claim || !metadata ? (
|
||||
<section>
|
||||
<span className="empty">{__('Empty claim or metadata info.')}</span>
|
||||
</section>
|
||||
) : (
|
||||
<section className="card">
|
||||
{showFile && (
|
||||
<FileViewer className="content__embedded" uri={uri} mediaType={mediaType} />
|
||||
)}
|
||||
{!showFile &&
|
||||
(thumbnail ? (
|
||||
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
||||
) : (
|
||||
<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)} />
|
||||
<Page forContent>
|
||||
<section className="content__wrapper">
|
||||
{showFile && <FileViewer className="content__embedded" uri={uri} mediaType={mediaType} />}
|
||||
{!showFile &&
|
||||
(thumbnail ? (
|
||||
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
||||
) : (
|
||||
<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>
|
||||
<span className="card__subtitle card__subtitle--file">
|
||||
{__('Published on')}
|
||||
<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">
|
||||
<FileDownloadLink uri={uri} />
|
||||
<FileActions uri={uri} claimId={claim.claim_id} />
|
||||
</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 filePage uri={normalizeURI(uri)} />
|
||||
</div>
|
||||
<FormRow padded>
|
||||
<ToolTip onComponent body={__('Automatically download and play free content.')}>
|
||||
<FormField
|
||||
useToggle
|
||||
name="autoplay"
|
||||
type="checkbox"
|
||||
postfix={__('Autoplay')}
|
||||
checked={autoplay}
|
||||
onChange={this.onAutoplayChange}
|
||||
</div>
|
||||
<span className="card__subtitle card__subtitle--file">
|
||||
{__('Published on')}
|
||||
<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');
|
||||
}}
|
||||
/>
|
||||
</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 className="card__content--extra-padding">
|
||||
<FileDetails uri={uri} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<RecommendedContent uri={uri} channelUri={`lbry://${subscriptionUri}`} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class SearchPage extends React.PureComponent<Props> {
|
|||
<Icon icon={icons.HELP} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
<FileTile fullWidth showUri displayHiddenMessage uri={normalizeURI(query)} />
|
||||
<FileTile showUri displayHiddenMessage uri={normalizeURI(query)} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
<FileListSearch query={query} />
|
||||
|
|
|
@ -120,7 +120,7 @@ export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch:
|
|||
thumbnail: `${json.data.url}${fileExt}`,
|
||||
},
|
||||
})
|
||||
: uploadError('Upload failed')
|
||||
: uploadError(json.message)
|
||||
)
|
||||
.catch(err => uploadError(err.message));
|
||||
};
|
||||
|
|
|
@ -74,7 +74,7 @@ export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => {
|
|||
|
||||
let supportedCoins = [];
|
||||
Object.keys(coinData).forEach(symbol => {
|
||||
if (coinData[symbol].status === SHAPESHIFT_STATUSES.UNAVAILABLE) {
|
||||
if (coinData[symbol].status === SHAPESHIFT_STATUSES.AVAILABLE) {
|
||||
supportedCoins.push(coinData[symbol]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -204,6 +204,12 @@ p {
|
|||
padding-right: 100px;
|
||||
}
|
||||
|
||||
.main--for-content {
|
||||
padding: $spacing-width * 2/3;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page__header {
|
||||
padding: $spacing-vertical * 2/3;
|
||||
padding-bottom: 0;
|
||||
|
@ -251,11 +257,10 @@ p {
|
|||
}
|
||||
|
||||
.credit-amount {
|
||||
border-radius: 5px;
|
||||
font-family: 'metropolis-bold';
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
white-space: nowrap;
|
||||
padding: $spacing-vertical * 1/6 0;
|
||||
}
|
||||
|
||||
.credit-amount--large {
|
||||
|
@ -263,30 +268,36 @@ p {
|
|||
font-size: 36px;
|
||||
}
|
||||
|
||||
.credit-amount--file-page {
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.credit-amount--free {
|
||||
color: var(--color-dark-blue);
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
|
||||
&.credit-amount--file-page {
|
||||
color: var(--color-dark-blue);
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.credit-amount--cost {
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-yellow);
|
||||
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;
|
||||
color: inherit;
|
||||
font-weight: 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';
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.divider__horizontal {
|
||||
|
|
|
@ -9,7 +9,7 @@ $large-breakpoint: 1921px;
|
|||
|
||||
:root {
|
||||
/* Widths & spacings */
|
||||
--side-nav-width: 220px;
|
||||
--side-nav-width: 190px;
|
||||
--side-nav-width-m: 240px;
|
||||
--side-nav-width-l: 320px;
|
||||
--font-size-subtext-multiple: 0.92;
|
||||
|
@ -155,9 +155,14 @@ $large-breakpoint: 1921px;
|
|||
--success-msg-border: var(--color-green-blue);
|
||||
--success-msg-bg: var(--color-green-light);
|
||||
|
||||
/* File Tile Card */
|
||||
--file-tile--media-height: 125px;
|
||||
--file-tile--media-width: calc(125px * (16 / 9));
|
||||
/* File */
|
||||
--file-tile-media-height: 125px;
|
||||
--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-width: 440px;
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
|
||||
@media only screen and (min-width: $medium-breakpoint) {
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,13 +113,17 @@
|
|||
.card__title--small {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding-top: $spacing-vertical / 3;
|
||||
|
||||
@media only screen and (min-width: $large-breakpoint) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.card__title--x-small {
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.card__title--file {
|
||||
font-family: 'metropolis-bold';
|
||||
font-size: 28px;
|
||||
|
@ -130,25 +133,36 @@
|
|||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card__title--file-card {
|
||||
padding-top: $spacing-vertical * 1/3;
|
||||
}
|
||||
|
||||
.card__subtitle {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-family: 'metropolis-medium';
|
||||
color: var(--card-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin-top: $spacing-vertical * 1/6;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin: 0 $spacing-vertical * 1/3;
|
||||
}
|
||||
margin: 0 0 0 $spacing-vertical * 1/3;
|
||||
}
|
||||
}
|
||||
|
||||
.card__subtitle--x-small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card__subtitle-price {
|
||||
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 {
|
||||
color: var(--color-help);
|
||||
font-size: 14px;
|
||||
|
@ -312,7 +326,11 @@
|
|||
|
||||
.card-row__scroll-btns {
|
||||
display: flex;
|
||||
padding-right: $spacing-width;
|
||||
padding-right: $spacing-width * 1/3;
|
||||
|
||||
@media (min-width: $medium-breakpoint) {
|
||||
padding-right: $spacing-width;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-left: 2px solid var(--success-msg-border);
|
||||
color: var(--success-msg-color);
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.content__wrapper {
|
||||
max-width: var(--card-max-width);
|
||||
flex: 1 0 var(--file-page-min-width);
|
||||
}
|
||||
|
||||
.content__embedded {
|
||||
background-color: var(--color-black);
|
||||
width: 100%;
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
|
||||
.file-tile {
|
||||
display: flex;
|
||||
margin-top: $spacing-vertical;
|
||||
padding-top: $spacing-vertical;
|
||||
|
||||
.card__media {
|
||||
height: var(--file-tile--media-height);
|
||||
flex: 0 0 var(--file-tile--media-width);
|
||||
height: var(--file-tile-media-height);
|
||||
flex: 0 0 var(--file-tile-media-width);
|
||||
}
|
||||
|
||||
.card__subtitle {
|
||||
|
@ -34,8 +34,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-tile--fullwidth {
|
||||
max-width: none;
|
||||
.file-tile.file-tile--small {
|
||||
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 {
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field__input.form-field--first-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-field__input {
|
||||
display: flex;
|
||||
padding-top: $spacing-vertical / 3;
|
||||
|
|
|
@ -6,9 +6,13 @@
|
|||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 $spacing-width;
|
||||
padding: 0 $spacing-width * 1/3;
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: var(--box-shadow-header);
|
||||
|
||||
@media (min-width: $medium-breakpoint) {
|
||||
padding: 0 $spacing-width;
|
||||
}
|
||||
}
|
||||
|
||||
.header__navigation {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.nav {
|
||||
width: var(--side-nav-width);
|
||||
background-color: var(--nav-bg-color);
|
||||
padding: $spacing-width;
|
||||
padding: $spacing-width * 1/3;
|
||||
color: var(--nav-color);
|
||||
|
||||
hr {
|
||||
|
@ -11,8 +11,13 @@
|
|||
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) {
|
||||
width: calc(var(--side-nav-width) * 1.1);
|
||||
width: calc(var(--side-nav-width) * 1.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
BIN
static/img/broken.png
Normal file
BIN
static/img/broken.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Loading…
Reference in a new issue