feat: setup Danger #1289

Merged
IGassmann merged 6 commits from setup-danger into master 2018-04-25 21:31:59 +02:00
183 changed files with 1966 additions and 3930 deletions
Showing only changes of commit b2af953bf1 - Show all commits

View file

@ -8,11 +8,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Added
* Add keyboard shortcut to quit the app on Windows ([#1202](https://github.com/lbryio/lbry-app/pull/1202))
* Build for both architectures (x86 and x64) for Windows ([#1262](https://github.com/lbryio/lbry-app/pull/1262))
* Add referral FAQ to Invites screen([#1314](https://github.com/lbryio/lbry-app/pull/1314))
* Show exact wallet balance on mouse hover over ([#1305](https://github.com/lbryio/lbry-app/pull/1305))
* New dark mode ([#1269](https://github.com/lbryio/lbry-app/pull/1269))
* Pre-fill publish URL after clicking "Put something here" link ([#1303](https://github.com/lbryio/lbry-app/pull/1303))
### Changed
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
### Fixed
* Black screen on macOS after maximizing LBRY and then closing ([#1235](https://github.com/lbryio/lbry-app/pull/1235))
### Fixed
* Black screen on macOS after maximizing LBRY and then closing ([#1235](https://github.com/lbryio/lbry-app/pull/1235))
* Fix dark theme ([#1034](https://github.com/lbryio/lbry-app/issues/1034))
* Fix download percentage indicator overlay ([#1271](https://github.com/lbryio/lbry-app/issues/1271))
* Fix alternate row shading for transactions on dark theme ([#1355](https://github.com/lbryio/lbry-app/issues/#1355))
* Fix Description box on Publish (dark theme) ([#1356](https://github.com/lbryio/lbry-app/issues/#1356))
## [0.21.2] - 2018-03-22

View file

@ -48,6 +48,7 @@
"find-process": "^1.1.0",
"formik": "^0.10.4",
"keytar": "^4.2.1",
"lbry-redux": "lbryio/lbry-redux",
"localforage": "^1.7.1",
"mixpanel-browser": "^2.17.1",
"moment": "^2.22.0",
@ -60,7 +61,7 @@
"react-modal": "^3.1.7",
"react-paginate": "^5.2.1",
"react-redux": "^5.0.3",
"react-simplemde-editor": "^3.6.11",
"react-simplemde-editor": "3.6.11",
"react-transition-group": "1.x",
"redux": "^3.6.0",
"redux-logger": "^3.0.1",

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { doShowSnackBar } from 'redux/actions/app';
import { doNotify } from 'lbry-redux';
import Address from './view';
export default connect(null, {
doShowSnackBar,
doNotify,
})(Address);

View file

@ -7,7 +7,7 @@ import * as icons from 'constants/icons';
type Props = {
address: string,
doShowSnackBar: ({ message: string }) => void,
doNotify: ({ message: string, displayType: Array<string> }) => void,
};
export default class Address extends React.PureComponent<Props> {
@ -20,10 +20,10 @@ export default class Address extends React.PureComponent<Props> {
input: ?HTMLInputElement;
render() {
const { address, doShowSnackBar } = this.props;
const { address, doNotify } = this.props;
return (
<FormRow verticallyCentered padded>
<FormRow verticallyCentered padded stretch>
<input
className="input-copyable form-field__input"
readOnly
@ -43,7 +43,10 @@ export default class Address extends React.PureComponent<Props> {
icon={icons.CLIPBOARD}
onClick={() => {
clipboard.writeText(address);
doShowSnackBar({ message: __('Address copied') });
doNotify({
message: __('Address copied'),
displayType: ['snackbar'],
});
}}
/>
</FormRow>

View file

@ -1,12 +1,8 @@
import { connect } from 'react-redux';
import {
selectPageTitle,
selectHistoryIndex,
selectActiveHistoryEntry,
} from 'redux/selectors/navigation';
import { selectPageTitle, selectHistoryIndex, selectActiveHistoryEntry } from 'lbry-redux';
import { doRecordScroll } from 'redux/actions/navigation';
import { selectUser } from 'redux/selectors/user';
import { doAlertError } from 'redux/actions/app';
import { doRecordScroll } from 'redux/actions/navigation';
import App from './view';
const select = state => ({

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'redux/actions/navigation';
import Button from './view';

View file

@ -22,9 +22,7 @@ const autoThumbColors = [
];
class CardMedia extends React.PureComponent<Props> {
getAutoThumbClass = () => {
return autoThumbColors[Math.floor(Math.random() * autoThumbColors.length)];
};
getAutoThumbClass = () => autoThumbColors[Math.floor(Math.random() * autoThumbColors.length)];
render() {
const { thumbnail, nsfw } = this.props;

View file

@ -158,7 +158,7 @@ class CardVerify extends React.Component {
render() {
return (
<Button
button="alt"
button="primary"
label={this.props.label}
icon={icons.LOCK}
disabled={this.props.disabled || this.state.open || this.hasPendingClick}

View file

@ -1,10 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import { doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from 'redux/actions/content';
import { makeSelectTotalItemsForChannel } from 'redux/selectors/content';
import { makeSelectIsUriResolving } from 'redux/selectors/content';
import ChannelTile from './view';
const select = (state, props) => ({

View file

@ -5,12 +5,10 @@ type Props = {
message: ?string,
};
const BusyIndicator = (props: Props) => {
return (
<span className="busy-indicator">
{props.message} <span className="busy-indicator__loader" />
</span>
);
};
const BusyIndicator = (props: Props) => (
<span className="busy-indicator">
{props.message} <span className="busy-indicator__loader" />
</span>
);
export default BusyIndicator;

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import { normalizeURI } from 'lbryURI';
import { normalizeURI } from 'lbry-redux';
import ToolTip from 'component/common/tooltip';
import FileCard from 'component/fileCard';
import Button from 'component/button';
@ -234,8 +234,7 @@ class CategoryList extends React.PureComponent<Props, State> {
}}
className="card-row__scrollhouse"
>
{names &&
names.map(name => <FileCard key={name} displayStyle="card" uri={normalizeURI(name)} />)}
{names && names.map(name => <FileCard key={name} uri={normalizeURI(name)} />)}
</div>
</div>
);

View file

@ -25,9 +25,10 @@ export class FormFieldPrice extends React.PureComponent<Props> {
handleAmountChange(event: SyntheticInputEvent<*>) {
const { price, onChange } = this.props;
const amount = event.target.value ? parseFloat(event.target.value) : '';
onChange({
currency: price.currency,
amount: parseFloat(event.target.value),
amount,
});
}
@ -50,7 +51,7 @@ export class FormFieldPrice extends React.PureComponent<Props> {
type="number"
className="form-field input--price-amount"
min={min}
value={price.amount || ''}
value={price.amount}
onChange={this.handleAmountChange}
placeholder={placeholder || 5}
disabled={disabled}

View file

@ -36,6 +36,8 @@ export class FormField extends React.PureComponent<Props> {
...inputProps
} = this.props;
const errorMessage = typeof error === 'object' ? error.message : error;
let input;
if (type) {
if (type === 'select') {
@ -54,6 +56,8 @@ export class FormField extends React.PureComponent<Props> {
/>
</div>
);
} else if (type === 'textarea') {
input = <textarea type={type} id={name} {...inputProps} />;
} else {
input = <input type={type} id={name} {...inputProps} />;
}
@ -65,13 +69,13 @@ export class FormField extends React.PureComponent<Props> {
'form-field--stretch': stretch || type === 'markdown',
})}
>
{(label || error) && (
{(label || errorMessage) && (
<label
className={classnames('form-field__label', { 'form-field__error': error })}
className={classnames('form-field__label', { 'form-field__error': errorMessage })}
htmlFor={name}
>
{!error && label}
{error}
{!errorMessage && label}
{errorMessage}
</label>
)}
<div

View file

@ -8,6 +8,7 @@ type Props = {
children: React.Node,
padded?: boolean,
verticallyCentered?: boolean,
stretch?: boolean,
};
export class FormRow extends React.PureComponent<Props> {
@ -16,13 +17,14 @@ export class FormRow extends React.PureComponent<Props> {
};
render() {
const { centered, children, padded, verticallyCentered } = this.props;
const { centered, children, padded, verticallyCentered, stretch } = this.props;
return (
<div
className={classnames('form-row', {
'form-row--centered': centered,
'form-row--padded': padded,
'form-row--vertically-centered': verticallyCentered,
'form-row--stretch': stretch,
})}
>
{children}

View file

@ -8,7 +8,6 @@ const RED_COLOR = '#e2495e';
type Props = {
icon: string,
size?: number,
};
class IconComponent extends React.PureComponent<Props> {

View file

@ -1,18 +1,30 @@
// @flow
import React from 'react';
import QRCodeElement from 'qrcode.react';
import classnames from 'classnames';
type Props = {
value: string,
paddingRight?: boolean,
};
const QRCode = (props: Props) => {
const { value } = props;
return (
<div className="qr-code">
<QRCodeElement value={value} />
</div>
);
};
class QRCode extends React.Component<Props> {
static defaultProps = {
paddingRight: false,
};
render() {
const { value, paddingRight } = this.props;
return (
<div
className={classnames('qr-code', {
'qr-code--right-padding': paddingRight,
})}
>
<QRCodeElement value={value} />
</div>
);
}
}
export default QRCode;

View file

@ -9,7 +9,7 @@ type Props = {
const TransactionLink = (props: Props) => {
const { id } = props;
const href = `https://explorer.lbry.io/#!/transaction/${id}`;
const href = `https://explorer.lbry.io/tx/${id}`;
const label = id.substr(0, 7);
return <Button button="link" href={href} label={label} />;

View file

@ -6,12 +6,10 @@ type Props = {
children: React.Node,
};
const TruncatedText = (props: Props) => {
return (
<span className="truncated-text" style={{ WebkitLineClamp: props.lines }}>
{props.children}
</span>
);
};
const TruncatedText = (props: Props) => (
<span className="truncated-text" style={{ WebkitLineClamp: props.lines }}>
{props.children}
</span>
);
export default TruncatedText;

View file

@ -1,7 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import { makeSelectBlockDate } from 'redux/selectors/wallet';
import { doFetchBlock } from 'redux/actions/wallet';
import { doFetchBlock, makeSelectBlockDate } from 'lbry-redux';
import DateTime from './view';
const select = (state, props) => ({

View file

@ -1,9 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { doOpenModal } from 'redux/actions/app';
import { makeSelectClaimIsMine } from 'redux/selectors/claims';
import {
makeSelectCostInfoForUri,
makeSelectFileInfoForUri,
makeSelectClaimIsMine,
doNotify,
} from 'lbry-redux';
import FileActions from './view';
const select = (state, props) => ({
@ -14,7 +15,7 @@ const select = (state, props) => ({
});
const perform = dispatch => ({
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
openModal: (modal, props) => dispatch(doNotify(modal, props)),
});
export default connect(select, perform)(FileActions);

View file

@ -12,7 +12,7 @@ type FileInfo = {
type Props = {
uri: string,
openModal: (string, any) => void,
openModal: ({ id: string }, { uri: string }) => void,
claimIsMine: boolean,
fileInfo: FileInfo,
vertical?: boolean, // should the buttons be stacked vertically?
@ -33,7 +33,7 @@ class FileActions extends React.PureComponent<Props> {
className="btn--file-actions"
icon={icons.TRASH}
description={__('Delete')}
onClick={() => openModal(modals.CONFIRM_FILE_REMOVE, { uri })}
onClick={() => openModal({ id: modals.CONFIRM_FILE_REMOVE }, { uri })}
/>
)}
{!claimIsMine && (

View file

@ -1,21 +1,22 @@
import React from 'react';
import { connect } from 'react-redux';
import {
doResolveUri,
makeSelectClaimForUri,
makeSelectMetadataForUri,
makeSelectFileInfoForUri,
makeSelectIsUriResolving,
} from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from 'redux/actions/content';
import { selectRewardContentClaimIds } from 'redux/selectors/content';
import { selectShowNsfw } from 'redux/selectors/settings';
import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content';
import { selectPendingPublish } from 'redux/selectors/publish';
import FileCard from './view';
const select = (state, props) => {
let claim;
let fileInfo;
let metadata;
let isResolvingUri;
const pendingPublish = selectPendingPublish(props.uri)(state);
let pendingPublish;
if (props.checkPending) {
pendingPublish = selectPendingPublish(props.uri)(state);
}
const fileCardInfo = pendingPublish || {
claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,19 +1,17 @@
// @flow
import * as React from 'react';
import { normalizeURI } from 'lbryURI';
import { normalizeURI } from 'lbry-redux';
import Button from 'component/button';
import CardMedia from 'component/cardMedia';
import TruncatedText from 'component/common/truncated-text';
import Icon from 'component/common/icon';
import FilePrice from 'component/filePrice';
import UriIndicator from 'component/uriIndicator';
import NsfwOverlay from 'component/nsfwOverlay';
import * as icons from 'constants/icons';
import classnames from 'classnames';
// TODO: iron these out
type Props = {
isResolvingUri: boolean,
resolveUri: string => void,
uri: string,
claim: ?{ claim_id: string },
fileInfo: ?{},
@ -78,22 +76,41 @@ class FileCard extends React.PureComponent<Props> {
<CardMedia nsfw={shouldObscureNsfw} thumbnail={thumbnail} />
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
<div className="card__title-identity">
<div className="card__title--small">
<TruncatedText lines={3}>{title}</TruncatedText>
{shouldObscureNsfw ? (
<div className="card__title-identity">
<div className="card__title--small">
<TruncatedText lines={3}>
{__('This content is obscured because it is NSFW. You can change this in ')}
<Button
button="link"
label={__('Settings.')}
onClick={e => {
// Don't propagate to the onClick handler of parent element
e.stopPropagation();
navigate('/settings');
}}
/>
</TruncatedText>
</div>
</div>
<div className="card__subtitle card__subtitle--file-info">
{pending ? (
<div>Pending...</div>
) : (
<React.Fragment>
<UriIndicator uri={uri} link />
{isRewardContent && <Icon icon={icons.FEATURED} />}
{fileInfo && <Icon icon={icons.LOCAL} />}
</React.Fragment>
)}
) : (
<div className="card__title-identity">
<div className="card__title--small">
<TruncatedText lines={3}>{title}</TruncatedText>
</div>
<div className="card__subtitle card__subtitle--file-info">
{pending ? (
<div>Pending...</div>
) : (
<React.Fragment>
<UriIndicator uri={uri} link />
{isRewardContent && <Icon icon={icons.FEATURED} />}
{fileInfo && <Icon icon={icons.LOCAL} />}
</React.Fragment>
)}
</div>
</div>
</div>
)}
</section>
);
/* eslint-enable jsx-a11y/click-events-have-key-events */

View file

@ -1,13 +1,12 @@
import React from 'react';
import { connect } from 'react-redux';
import {
makeSelectClaimForUri,
makeSelectContentTypeForUri,
makeSelectMetadataForUri,
} from 'redux/selectors/claims';
makeSelectFileInfoForUri,
} from 'lbry-redux';
import { doOpenFileInFolder } from 'redux/actions/file';
import FileDetails from './view';
import { doOpenFileInFolder } from 'redux/actions/file_info';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
import ReactMarkdown from 'react-markdown';
import lbry from 'lbry';
import { Lbry } from 'lbry-redux';
import Button from 'component/button';
import path from 'path';
@ -31,7 +31,7 @@ const FileDetails = (props: Props) => {
}
const { description, language, license } = metadata;
const mediaType = lbry.getMediaType(contentType);
const mediaType = Lbry.getMediaType(contentType);
const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null;

View file

@ -1,13 +1,12 @@
import React from 'react';
import { connect } from 'react-redux';
import {
makeSelectFileInfoForUri,
makeSelectDownloadingForUri,
makeSelectLoadingForUri,
} from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
makeSelectCostInfoForUri,
} from 'lbry-redux';
import { doFetchAvailability } from 'redux/actions/availability';
import { doOpenFileInShell } from 'redux/actions/file_info';
import { doOpenFileInShell } from 'redux/actions/file';
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
import { doPause } from 'redux/actions/media';
import FileDownloadLink from './view';

View file

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import FileList from './view';
import { selectClaimsById } from 'redux/selectors/claims';
import { selectClaimsById } from 'lbry-redux';
const select = state => ({
claimsById: selectClaimsById(state),

View file

@ -1,6 +1,6 @@
// @flow
import * as React from 'react';
import { buildURI } from 'lbryURI';
import { buildURI } from 'lbry-redux';
import { FormField } from 'component/common/form';
import FileCard from 'component/fileCard';
@ -22,7 +22,10 @@ type FileInfo = {
type Props = {
hideFilter: boolean,
sortByHeight?: boolean,
claimsById: Array<{}>,
fileInfos: Array<FileInfo>,
checkPending?: boolean,
};
type State = {
@ -41,6 +44,8 @@ class FileList extends React.PureComponent<Props, State> {
sortBy: 'dateNew',
};
(this: any).handleSortChanged = this.handleSortChanged.bind(this);
this.sortFunctions = {
dateNew: fileInfos =>
this.props.sortByHeight
@ -136,26 +141,35 @@ class FileList extends React.PureComponent<Props, State> {
}
render() {
const { fileInfos, hideFilter } = this.props;
const { fileInfos, hideFilter, checkPending } = this.props;
const { sortBy } = this.state;
const content = [];
this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
const { channel_name: channelName, name: claimName, claim_id: claimId } = fileInfo;
const {
channel_name: channelName,
name: claimName,
claim_name: claimNameDownloaded,
claim_id: claimId,
} = fileInfo;
const uriParams = {};
// This is unfortunate
// https://github.com/lbryio/lbry/issues/1159
const name = claimName || claimNameDownloaded;
if (channelName) {
uriParams.channelName = channelName;
uriParams.contentName = claimName;
uriParams.contentName = name;
uriParams.claimId = this.getChannelSignature(fileInfo);
} else {
uriParams.claimId = claimId;
uriParams.claimName = claimName;
uriParams.claimName = name;
}
const uri = buildURI(uriParams);
content.push(<FileCard key={claimName} uri={uri} showPrice={false} />);
content.push(<FileCard key={uri} uri={uri} checkPending={checkPending} />);
});
return (
@ -168,7 +182,8 @@ class FileList extends React.PureComponent<Props, State> {
value={sortBy}
onChange={this.handleSortChanged}
>
<option value="date">{__('Date')}</option>
<option value="dateNew">{__('Newest First')}</option>
<option value="dateOld">{__('Oldest First')}</option>
<option value="title">{__('Title')}</option>
</FormField>
)}

View file

@ -1,7 +1,10 @@
import { connect } from 'react-redux';
import { doSearch } from 'redux/actions/search';
import { makeSelectSearchUris, selectIsSearching } from 'redux/selectors/search';
import { selectSearchDownloadUris } from 'redux/selectors/file_info';
import {
doSearch,
makeSelectSearchUris,
selectIsSearching,
selectSearchDownloadUris,
} from 'lbry-redux';
import FileListSearch from './view';
const select = (state, props) => ({

View file

@ -2,14 +2,12 @@
import React from 'react';
import FileTile from 'component/fileTile';
import ChannelTile from 'component/channelTile';
import { parseURI } from 'lbryURI';
import { parseURI } from 'lbry-redux';
import debounce from 'util/debounce';
const SEARCH_DEBOUNCE_TIME = 800;
const NoResults = () => {
return <div className="file-tile">{__('No results')}</div>;
};
const NoResults = () => <div className="file-tile">{__('No results')}</div>;
type Props = {
search: string => void,

View file

@ -1,11 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
import {
doFetchCostInfoForUri,
makeSelectCostInfoForUri,
makeSelectFetchingCostInfoForUri,
} from 'redux/selectors/cost_info';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
makeSelectClaimForUri,
} from 'lbry-redux';
import FilePrice from './view';
const select = (state, props) => ({

View file

@ -1,11 +1,14 @@
import React from 'react';
import { connect } from 'react-redux';
import {
doResolveUri,
makeSelectClaimForUri,
makeSelectMetadataForUri,
makeSelectFileInfoForUri,
makeSelectIsUriResolving,
} from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doResolveUri } from 'redux/actions/content';
import { makeSelectClaimForUri, makeSelectMetadataForUri } from 'redux/selectors/claims';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectShowNsfw } from 'redux/selectors/settings';
import { makeSelectIsUriResolving, selectRewardContentClaimIds } from 'redux/selectors/content';
import { doClearPublish, doUpdatePublishForm } from 'redux/actions/publish';
import { selectRewardContentClaimIds } from 'redux/selectors/content';
import FileTile from './view';
const select = (state, props) => ({
@ -17,8 +20,10 @@ const select = (state, props) => ({
});
const perform = dispatch => ({
clearPublish: () => dispatch(doClearPublish()),
navigate: (path, params) => dispatch(doNavigate(path, params)),
resolveUri: uri => dispatch(doResolveUri(uri)),
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
});
export default connect(select, perform)(FileTile);

View file

@ -1,10 +1,9 @@
// @flow
import * as React from 'react';
import * as icons from 'constants/icons';
import { normalizeURI, isURIClaimable, parseURI } from 'lbryURI';
import { normalizeURI, isURIClaimable, parseURI } from 'lbry-redux';
import CardMedia from 'component/cardMedia';
import TruncatedText from 'component/common/truncated-text';
import FilePrice from 'component/filePrice';
import Icon from 'component/common/icon';
import Button from 'component/button';
import classnames from 'classnames';
@ -25,6 +24,8 @@ type Props = {
metadata: {},
resolveUri: string => void,
navigate: (string, ?{}) => void,
clearPublish: () => void,
updatePublishForm: () => void,
};
class FileTile extends React.PureComponent<Props> {
@ -55,6 +56,8 @@ class FileTile extends React.PureComponent<Props> {
fullWidth,
showLocal,
isDownloaded,
clearPublish,
updatePublishForm,
} = this.props;
const uri = normalizeURI(this.props.uri);
@ -69,7 +72,7 @@ class FileTile extends React.PureComponent<Props> {
let name;
let channel;
if (claim) {
name = claim.name;
({ name } = claim);
channel = claim.channel_name;
}
@ -79,6 +82,9 @@ class FileTile extends React.PureComponent<Props> {
'file-tile--fullwidth': fullWidth,
})}
onClick={onClick}
onKeyUp={onClick}
role="button"
tabIndex="0"
>
<CardMedia title={title || name} thumbnail={thumbnail} />
<div className="file-tile__info">
@ -101,7 +107,11 @@ class FileTile extends React.PureComponent<Props> {
label={__('Put something here!')}
onClick={e => {
// avoid navigating to /show from clicking on the section
e.preventDefault();
e.stopPropagation();
// strip prefix from the Uri and use that as navigation parameter
const nameFromUri = uri.replace(/lbry:\/\//g, '').replace(/-/g, ' ');
clearPublish(); // to remove any existing publish data
updatePublishForm({ name: nameFromUri }); // to populate the name
navigate('/publish');
}}
/>

View file

@ -1,22 +1,21 @@
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { formatCredits } from 'util/formatCredits';
import { doNavigate } from 'redux/actions/navigation';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
import { formatCredits } from 'util/formatCredits';
import { selectBalance } from 'redux/selectors/wallet';
import Header from './view';
import { doDownloadUpgradeRequested } from 'redux/actions/app';
import Header from './view';
const select = state => ({
isUpgradeAvailable: selectIsUpgradeAvailable(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
balance: formatCredits(selectBalance(state) || 0, 2),
balance: selectBalance(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
roundedBalance: formatCredits(selectBalance(state) || 0, 2),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
forward: () => dispatch(doHistoryForward()),
downloadUpgradeRequested: () => dispatch(doDownloadUpgradeRequested()),
navigate: path => dispatch(doNavigate(path)),
});
export default connect(select, perform)(Header);

View file

@ -5,20 +5,22 @@ import WunderBar from 'component/wunderbar';
import * as icons from 'constants/icons';
type Props = {
autoUpdateDownloaded: boolean,
balance: string,
navigate: any => void,
downloadUpgradeRequested: any => void,
isUpgradeAvailable: boolean,
autoUpdateDownloaded: boolean,
navigate: any => void,
roundedBalance: string,
};
const Header = (props: Props) => {
const {
autoUpdateDownloaded,
balance,
downloadUpgradeRequested,
isUpgradeAvailable,
navigate,
downloadUpgradeRequested,
autoUpdateDownloaded,
roundedBalance,
} = props;
const showUpgradeButton =
@ -37,7 +39,10 @@ const Header = (props: Props) => {
`${balance}`
) : (
<React.Fragment>
<span className="btn__label--balance">You have</span> <span>{balance} LBC</span>
<span className="btn__label--balance" title={`${balance} LBC`}>
You have
</span>{' '}
<span title={`${balance} LBC`}>{roundedBalance} LBC</span>
</React.Fragment>
)
}
@ -48,6 +53,7 @@ const Header = (props: Props) => {
<Button
uppercase
button="primary"
className="btn--header-publish"
onClick={() => navigate('/publish')}
icon={icons.UPLOAD}
label={isUpgradeAvailable ? '' : __('Publish')}

View file

@ -3,6 +3,7 @@
import React from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import CreditAmount from 'component/common/credit-amount';
import Button from 'component/button';
import { Form, FormRow, FormField, Submit } from 'component/common/form';
class FormInviteNew extends React.PureComponent {
@ -69,6 +70,7 @@ class InviteNew extends React.PureComponent {
return (
<section className="card card--section">
<div className="card__title">{__('Invite a Friend')}</div>
<div className="card__subtitle">
{__("Or an enemy. Or your cousin Jerry, who you're kind of unsure about.")}
</div>
@ -87,6 +89,11 @@ class InviteNew extends React.PureComponent {
rewardAmount={rewardAmount}
/>
</div>
<p className="help help--padded">
{__('Read our')}{' '}
<Button button="link" label={__('FAQ')} href="https://lbry.io/faq/referrals" />{' '}
{__('to learn more about referrals')}.
</p>
</section>
);
}

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'redux/actions/navigation';
import NsfwOverlay from './view';

View file

@ -1,15 +1,15 @@
import { connect } from 'react-redux';
import {
selectBalance,
selectPageTitle,
selectIsBackDisabled,
selectIsForwardDisabled,
selectNavLinks,
} from 'redux/selectors/navigation';
} from 'lbry-redux';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import { doDownloadUpgrade } from 'redux/actions/app';
import { selectIsUpgradeAvailable } from 'redux/selectors/app';
import { formatCredits } from 'util/formatCredits';
import { selectBalance } from 'redux/selectors/wallet';
import Page from './view';
const select = state => ({

View file

@ -1,5 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import PublishForm from './view';
export default connect(null, null)(PublishForm);

View file

@ -7,7 +7,7 @@ type Props = {
editingURI: ?string,
isResolvingUri: boolean,
winningBidForClaimUri: ?number,
claimIsMine: ?boolean,
myClaimForUri: ?{},
onEditMyClaim: any => void,
};
@ -18,7 +18,7 @@ class BidHelpText extends React.PureComponent<Props> {
editingURI,
isResolvingUri,
winningBidForClaimUri,
claimIsMine,
myClaimForUri,
onEditMyClaim,
} = this.props;
@ -34,12 +34,12 @@ class BidHelpText extends React.PureComponent<Props> {
return __('Checking the winning claim amount...');
}
if (claimIsMine) {
if (myClaimForUri) {
return (
<React.Fragment>
{__('You already have a claim at')}
{` ${uri} `}
<Button button="link" label="Edit it" onClick={onEditMyClaim} />
<Button button="link" label="Edit it" onClick={() => onEditMyClaim(myClaimForUri, uri)} />
<br />
{__('Publishing will update your existing claim.')}
</React.Fragment>

View file

@ -1,6 +1,6 @@
// @flow
import * as React from 'react';
import { isNameValid, buildURI, regexInvalidURI } from 'lbryURI';
import { isNameValid, buildURI, regexInvalidURI } from 'lbry-redux';
import { Form, FormField, FormRow, FormFieldPrice, Submit } from 'component/common/form';
import Button from 'component/button';
import ChannelSection from 'component/selectChannel';
@ -38,6 +38,11 @@ type Props = {
winningBidForClaimUri: number,
myClaimForUri: ?{
amount: number,
value: {
stream: {
source: { source: string },
},
},
},
licenseType: string,
otherLicenseDescription: ?string,
@ -50,7 +55,7 @@ type Props = {
clearPublish: () => void,
resolveUri: string => void,
scrollToTop: () => void,
prepareEdit: ({}) => void,
prepareEdit: ({}, uri) => void,
};
class PublishForm extends React.PureComponent<Props> {
@ -68,85 +73,43 @@ class PublishForm extends React.PureComponent<Props> {
(this: any).getNewUri = this.getNewUri.bind(this);
}
handlePublish() {
const {
publish,
filePath,
bid,
title,
thumbnail,
description,
language,
nsfw,
channel,
licenseType,
licenseUrl,
otherLicenseDescription,
copyrightNotice,
name,
contentIsFree,
price,
uri,
} = this.props;
// Returns a new uri to be used in the form and begins to resolve that uri for bid help text
getNewUri(name: string, channel: string) {
const { resolveUri } = this.props;
// If they are midway through a channel creation, treat it as anonymous until it completes
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
let publishingLicense;
switch (licenseType) {
case COPYRIGHT:
publishingLicense = copyrightNotice;
break;
case OTHER:
publishingLicense = otherLicenseDescription;
break;
default:
publishingLicense = licenseType;
let uri;
try {
uri = buildURI({ contentName: name, channelName });
} catch (e) {
// something wrong with channel or name
}
const publishingLicenseUrl = licenseType === COPYRIGHT ? '' : licenseUrl;
const publishParams = {
filePath,
bid,
title,
thumbnail,
description,
language,
nsfw,
channel,
license: publishingLicense,
licenseUrl: publishingLicenseUrl,
otherLicenseDescription,
copyrightNotice,
name,
contentIsFree,
price,
uri,
};
publish(publishParams);
}
handleCancelPublish() {
const { clearPublish, scrollToTop } = this.props;
scrollToTop();
clearPublish();
}
editExistingClaim() {
const { myClaimForUri, prepareEdit, scrollToTop } = this.props;
if (myClaimForUri) {
prepareEdit(myClaimForUri);
scrollToTop();
if (uri) {
resolveUri(uri);
return uri;
}
return '';
}
handleFileChange(filePath: string, fileName: string) {
const { updatePublishForm, channel } = this.props;
const parsedFileName = fileName.replace(regexInvalidURI, '');
const uri = this.getNewUri(parsedFileName, channel);
const { updatePublishForm, channel, name } = this.props;
const newFileParams: {
filePath: string,
name?: string,
uri?: string,
} = { filePath };
if (filePath) {
updatePublishForm({ filePath, name: parsedFileName, uri });
if (!name) {
const parsedFileName = fileName.replace(regexInvalidURI, '');
const uri = this.getNewUri(parsedFileName, channel);
newFileParams.name = parsedFileName;
newFileParams.uri = uri;
}
updatePublishForm(newFileParams);
}
handleNameChange(name: ?string) {
@ -196,25 +159,82 @@ class PublishForm extends React.PureComponent<Props> {
updatePublishForm({ bid, bidError });
}
// Returns a new uri to be used in the form and begins to resolve that uri for bid help text
getNewUri(name: string, channel: string) {
const { resolveUri } = this.props;
// If they are midway through a channel creation, treat it as anonymous until it completes
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
editExistingClaim(myClaimForUri: ?{}, uri: string) {
const { prepareEdit, scrollToTop } = this.props;
if (myClaimForUri) {
prepareEdit(myClaimForUri, uri);
scrollToTop();
}
}
let uri;
try {
uri = buildURI({ contentName: name, channelName });
} catch (e) {
// something wrong with channel or name
handleCancelPublish() {
const { clearPublish, scrollToTop } = this.props;
scrollToTop();
clearPublish();
}
handlePublish() {
const {
publish,
filePath,
bid,
title,
thumbnail,
description,
language,
nsfw,
channel,
licenseType,
licenseUrl,
otherLicenseDescription,
copyrightNotice,
name,
contentIsFree,
price,
uri,
myClaimForUri,
} = this.props;
let publishingLicense;
switch (licenseType) {
case COPYRIGHT:
publishingLicense = copyrightNotice;
break;
case OTHER:
publishingLicense = otherLicenseDescription;
break;
default:
publishingLicense = licenseType;
}
if (uri) {
resolveUri(uri);
return uri;
const publishingLicenseUrl = licenseType === COPYRIGHT ? '' : licenseUrl;
const publishParams = {
filePath,
bid,
title,
thumbnail,
description,
language,
nsfw,
channel,
license: publishingLicense,
licenseUrl: publishingLicenseUrl,
otherLicenseDescription,
copyrightNotice,
name,
contentIsFree,
price,
uri,
};
// Editing a claim
if (!filePath && myClaimForUri) {
const { source } = myClaimForUri.value.stream;
publishParams.sources = source;
}
return '';
publish(publishParams);
}
checkIsFormValid() {
@ -280,7 +300,8 @@ class PublishForm extends React.PureComponent<Props> {
const formDisabled = (!filePath && !editingURI) || publishing;
const formValid = this.checkIsFormValid();
const isStillEditing = editingURI === uri;
const simpleUri = uri && uri.split('#')[0];
const isStillEditing = editingURI === simpleUri;
let submitLabel;
if (isStillEditing) {
submitLabel = !publishing ? __('Edit') : __('Editing...');
@ -374,14 +395,14 @@ class PublishForm extends React.PureComponent<Props> {
disabled={formDisabled}
onChange={() => updatePublishForm({ contentIsFree: false })}
/>
<FormFieldPrice
name="content_cost_amount"
min="0"
price={price}
onChange={newPrice => updatePublishForm({ price: newPrice })}
disabled={formDisabled || contentIsFree}
/>
{!contentIsFree && (
<FormFieldPrice
name="content_cost_amount"
min="0"
price={price}
onChange={newPrice => updatePublishForm({ price: newPrice })}
/>
)}
{price.currency !== 'LBC' && (
<p className="form-field__help">
{__(
@ -414,7 +435,9 @@ class PublishForm extends React.PureComponent<Props> {
<FormField
stretch
prefix={`lbry://${
channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW
? ''
: `${channel}/`
}`}
type="text"
name="content_name"
@ -424,11 +447,11 @@ class PublishForm extends React.PureComponent<Props> {
error={nameError}
helper={
<BidHelpText
uri={uri}
uri={simpleUri}
editingURI={editingURI}
isResolvingUri={isResolvingUri}
winningBidForClaimUri={winningBidForClaimUri}
claimIsMine={!!myClaimForUri}
myClaimForUri={myClaimForUri}
onEditMyClaim={this.editExistingClaim}
/>
}

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import {
makeSelectClaimRewardError,

View file

@ -1,14 +1,28 @@
// @flow
import React from 'react';
import Modal from 'modal/modal';
import { Modal } from 'modal/modal';
import Button from 'component/button';
const RewardLink = props => {
const { reward, button, claimReward, clearError, errorMessage, label, isPending } = props;
type Reward = {
reward_amount: number,
};
type Props = {
isPending: boolean,
label: ?string,
errorMessage: ?string,
reward: Reward,
clearError: Reward => void,
claimReward: Reward => void,
};
const RewardLink = (props: Props) => {
const { reward, claimReward, clearError, errorMessage, label, isPending } = props;
return !reward ? null : (
<div className="reward-link">
<Button
button="primary"
button="link"
disabled={isPending}
label={isPending ? __('Claiming...') : label || `${__('Get')} ${reward.reward_amount} LBC`}
onClick={() => {
@ -16,6 +30,7 @@ const RewardLink = props => {
}}
/>
{errorMessage ? (
// TODO: This should be moved to redux
<Modal
isOpen
contentLabel="Reward Claim Error"
@ -32,4 +47,5 @@ const RewardLink = props => {
</div>
);
};
export default RewardLink;

View file

@ -1,7 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import Router from './view.jsx';
import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation.js';
import { selectCurrentPage, selectCurrentParams } from 'lbry-redux';
import Router from './view';
const select = state => ({
params: selectCurrentParams(state),

View file

@ -1,9 +1,9 @@
import React from 'react';
import SettingsPage from 'page/settings';
import HelpPage from 'page/help';
import ReportPage from 'page/report.js';
import ReportPage from 'page/report';
import WalletPage from 'page/wallet';
import GetCreditsPage from '../../page/getCredits';
import GetCreditsPage from 'page/getCredits';
import SendReceivePage from 'page/sendCredits';
import ShowPage from 'page/show';
import PublishPage from 'page/publish';

View file

@ -1,8 +1,7 @@
import { connect } from 'react-redux';
import SelectChannel from './view';
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
import { selectBalance, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
import { doFetchChannelListMine, doCreateChannel } from 'redux/actions/content';
import { selectBalance } from 'redux/selectors/wallet';
const select = state => ({
channels: selectMyChannelClaims(state),

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import { isNameValid } from 'lbryURI';
import { isNameValid } from 'lbry-redux';
import { FormRow, FormField } from 'component/common/form';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';

View file

@ -6,8 +6,7 @@ import {
clearShapeShift,
getActiveShift,
} from 'redux/actions/shape_shift';
import { doShowSnackBar } from 'redux/actions/app';
import { selectReceiveAddress } from 'redux/selectors/wallet';
import { selectReceiveAddress } from 'lbry-redux';
import { selectShapeShift } from 'redux/selectors/shape_shift';
import ShapeShift from './view';
@ -22,5 +21,4 @@ export default connect(select, {
createShapeShift,
clearShapeShift,
getActiveShift,
doShowSnackBar,
})(ShapeShift);

View file

@ -16,7 +16,6 @@ type Props = {
shiftOrderId: ?string,
originCoinDepositMax: ?number,
clearShapeShift: Dispatch,
doShowSnackBar: Dispatch,
getActiveShift: Dispatch,
shapeShiftRate: ?number,
originCoinDepositMax: ?number,
@ -25,8 +24,6 @@ type Props = {
};
class ActiveShapeShift extends React.PureComponent<Props> {
continousFetch: ?number;
constructor() {
super();
this.continousFetch = undefined;
@ -59,6 +56,8 @@ class ActiveShapeShift extends React.PureComponent<Props> {
}
}
continousFetch: ?number;
render() {
const {
shiftCoinType,
@ -68,7 +67,6 @@ class ActiveShapeShift extends React.PureComponent<Props> {
shiftState,
originCoinDepositMax,
clearShapeShift,
doShowSnackBar,
shapeShiftRate,
originCoinDepositFee,
originCoinDepositMin,
@ -95,8 +93,8 @@ class ActiveShapeShift extends React.PureComponent<Props> {
{shiftDepositAddress && (
<FormRow verticallyCentered padded>
<Address address={shiftDepositAddress} showCopyButton />
<QRCode value={shiftDepositAddress} />
<QRCode value={shiftDepositAddress} paddingRight />
<Address address={shiftDepositAddress} showCopyButton padded />
</FormRow>
)}
</div>

View file

@ -19,7 +19,6 @@ type Props = {
isSubmitting: boolean,
shiftSupportedCoins: Array<string>,
originCoin: string,
updating: boolean,
getCoinStats: Dispatch,
originCoinDepositFee: number,
originCoinDepositMin: string,
@ -38,7 +37,6 @@ export default (props: Props) => {
isSubmitting,
shiftSupportedCoins,
originCoin,
updating,
getCoinStats,
originCoinDepositMax,
originCoinDepositMin,
@ -51,7 +49,7 @@ export default (props: Props) => {
prefix={__('Exchange')}
postfix={__('for LBC')}
type="select"
name="origin_coin"
name="originCoin"
onChange={e => {
getCoinStats(e.target.value);
handleChange(e);
@ -76,7 +74,7 @@ export default (props: Props) => {
label={__('Return address')}
error={touched.returnAddress && !!errors.returnAddress && errors.returnAddress}
type="text"
name="return_address"
name="returnAddress"
className="input--address"
placeholder={getExampleAddress(originCoin)}
onChange={handleChange}

View file

@ -14,14 +14,16 @@ type Props = {
createShapeShift: Dispatch,
clearShapeShift: Dispatch,
getActiveShift: Dispatch,
doShowSnackBar: Dispatch,
shapeShiftInit: Dispatch,
receiveAddress: string,
};
class ShapeShift extends React.PureComponent<Props> {
componentDidMount() {
const { shapeShiftInit, shapeShift: { hasActiveShift, shiftSupportedCoins } } = this.props;
const {
shapeShiftInit,
shapeShift: { hasActiveShift, shiftSupportedCoins },
} = this.props;
if (!hasActiveShift && !shiftSupportedCoins.length) {
// calls shapeshift to see list of supported coins for shifting
@ -37,7 +39,6 @@ class ShapeShift extends React.PureComponent<Props> {
shapeShift,
clearShapeShift,
getActiveShift,
doShowSnackBar,
} = this.props;
const {
@ -107,7 +108,6 @@ class ShapeShift extends React.PureComponent<Props> {
shiftOrderId={shiftOrderId}
shiftState={shiftState}
clearShapeShift={clearShapeShift}
doShowSnackBar={doShowSnackBar}
originCoinDepositMax={originCoinDepositMax}
originCoinDepositMin={originCoinDepositMin}
originCoinDepositFee={originCoinDepositFee}

View file

@ -1,10 +1,6 @@
import { connect } from 'react-redux';
import { doNavigate, doHistoryBack, doHistoryForward } from 'redux/actions/navigation';
import {
selectIsBackDisabled,
selectIsForwardDisabled,
selectNavLinks,
} from 'redux/selectors/navigation';
import { selectIsBackDisabled, selectIsForwardDisabled, selectNavLinks } from 'lbry-redux';
import { selectNotifications } from 'redux/selectors/subscriptions';
import SideBar from './view';

View file

@ -1,15 +1,13 @@
import React from 'react';
import { connect } from 'react-redux';
import { doRemoveSnackBarSnack } from 'redux/actions/app';
import { selectSnackBarSnacks } from 'redux/selectors/app';
import { selectSnack, doHideNotification } from 'lbry-redux';
import SnackBar from './view';
const perform = dispatch => ({
removeSnack: () => dispatch(doRemoveSnackBarSnack()),
removeSnack: () => dispatch(doHideNotification()),
});
const select = state => ({
snacks: selectSnackBarSnacks(state),
snack: selectSnack(state),
});
export default connect(select, perform)(SnackBar);

View file

@ -1,35 +1,47 @@
// @flow
import React from 'react';
import Button from 'component/button';
class SnackBar extends React.PureComponent {
constructor(props) {
type Props = {
removeSnack: any => void,
snack: ?{
linkTarget: ?string,
linkText: ?string,
message: string,
},
};
class SnackBar extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this._displayTime = 5; // in seconds
this._hideTimeout = null;
this.displayTime = 5; // in seconds
this.hideTimeout = null;
}
render() {
const { snacks, removeSnack } = this.props;
const { snack, removeSnack } = this.props;
if (!snacks.length) {
this._hideTimeout = null; // should be unmounting anyway, but be safe?
if (!snack) {
this.hideTimeout = null; // should be unmounting anyway, but be safe?
return null;
}
const snack = snacks[0];
const { message, linkText, linkTarget } = snack;
if (this._hideTimeout === null) {
this._hideTimeout = setTimeout(() => {
this._hideTimeout = null;
if (this.hideTimeout === null) {
this.hideTimeout = setTimeout(() => {
this.hideTimeout = null;
removeSnack();
}, this._displayTime * 1000);
}, this.displayTime * 1000);
}
return (
<div className="snack-bar">
{message}
<div className="snack-bar__message">
<div>&#9432;</div>
<div>{message}</div>
</div>
{linkText &&
linkTarget && (
<Button navigate={linkTarget} className="snack-bar__action" label={linkText} />

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import lbry from 'lbry';
import { Lbry } from 'lbry-redux';
import LoadScreen from './internal/load-screen';
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from 'modal/modalUpgrade';
@ -31,7 +31,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
}
updateStatus() {
lbry.status().then(status => {
Lbry.status().then(status => {
this._updateStatusCallback(status);
});
}
@ -50,7 +50,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
isRunning: true,
});
lbry.resolve({ uri: 'lbry://one' }).then(() => {
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
// Only leave the load screen if the daemon version matched;
// otherwise we'll notify the user at the end of the load screen.
@ -83,8 +83,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
componentDidMount() {
const { checkDaemonVersion } = this.props;
lbry
.connect()
Lbry.connect()
.then(checkDaemonVersion)
.then(() => {
this.updateStatus();

View file

@ -1,18 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentPage, selectHeaderLinks } from 'redux/selectors/navigation';
import { doNavigate } from 'redux/actions/navigation';
import { selectNotifications } from 'redux/selectors/subscriptions';
import SubHeader from './view';
const select = (state, props) => ({
currentPage: selectCurrentPage(state),
subLinks: selectHeaderLinks(state),
notifications: selectNotifications(state),
});
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
});
export default connect(select, perform)(SubHeader);

View file

@ -1,42 +0,0 @@
import React from 'react';
import Link from 'component/link';
import classnames from 'classnames';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
const SubHeader = props => {
const { subLinks, currentPage, navigate, fullWidth, smallMargin, notifications } = props;
const badges = Object.keys(notifications).reduce(
(acc, cur) => (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING ? acc : acc + 1),
0
);
const links = [];
for (const link of Object.keys(subLinks)) {
links.push(
<Link
onClick={event => navigate(`/${link}`, event)}
key={link}
className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected'}
>
{subLinks[link] === 'Subscriptions' && badges
? `Subscriptions (${badges})`
: subLinks[link]}
</Link>
);
}
return (
<nav
className={classnames('sub-header', {
'sub-header--full-width': fullWidth,
'sub-header--small-margin': smallMargin,
})}
>
{links}
</nav>
);
};
export default SubHeader;

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { doOpenModal } from 'redux/actions/app';
import { doNotify } from 'lbry-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import SubscribeButton from './view';
@ -11,5 +11,5 @@ const select = (state, props) => ({
export default connect(select, {
doChannelSubscribe,
doChannelUnsubscribe,
doOpenModal,
doNotify,
})(SubscribeButton);

View file

@ -16,7 +16,7 @@ type Props = {
subscriptions: Array<Subscription>,
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
doChannelUnsubscribe: SubscribtionArgs => void,
doOpenModal: string => void,
doNotify: ({ id: string }) => void,
};
export default (props: Props) => {
@ -26,7 +26,7 @@ export default (props: Props) => {
subscriptions,
doChannelSubscribe,
doChannelUnsubscribe,
doOpenModal,
doNotify,
} = props;
const isSubscribed =
@ -42,7 +42,7 @@ export default (props: Props) => {
label={subscriptionLabel}
onClick={() => {
if (!subscriptions.length) {
doOpenModal(modals.FIRST_SUBSCRIPTION);
doNotify({ id: modals.FIRST_SUBSCRIPTION });
}
subscriptionHandler({
channelName,

View file

@ -1,9 +1,8 @@
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doOpenModal } from 'redux/actions/app';
import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
import { selectAllMyClaimsByOutpoint } from 'redux/selectors/claims';
import { doNavigate } from 'redux/actions/navigation';
import { doNotify } from 'lbry-redux';
import { selectAllMyClaimsByOutpoint } from 'lbry-redux';
import TransactionList from './view';
const select = state => ({
@ -13,7 +12,7 @@ const select = state => ({
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
openModal: (modal, props) => dispatch(doNotify(modal, props)),
});
export default connect(select, perform)(TransactionList);

View file

@ -4,7 +4,7 @@ import ButtonTransaction from 'component/common/transaction-link';
import CreditAmount from 'component/common/credit-amount';
import DateTime from 'component/dateTime';
import Button from 'component/button';
import { buildURI } from 'lbryURI';
import { buildURI } from 'lbry-redux';
import * as txnTypes from 'constants/transaction_types';
import type { Transaction } from '../view';

View file

@ -23,7 +23,7 @@ type Props = {
slim?: boolean,
transactions: Array<Transaction>,
rewards: {},
openModal: (string, any) => void,
openModal: ({ id: string }, { nout: number, txid: string }) => void,
myClaims: any,
};
@ -65,7 +65,7 @@ class TransactionList extends React.PureComponent<Props, State> {
}
revokeClaim(txid: string, nout: number) {
this.props.openModal(modals.CONFIRM_CLAIM_REVOKE, { txid, nout });
this.props.openModal({ id: modals.CONFIRM_CLAIM_REVOKE }, { txid, nout });
}
render() {

View file

@ -1,13 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
import { doFetchTransactions } from 'redux/actions/wallet';
import {
selectBalance,
doFetchTransactions,
selectRecentTransactions,
selectHasTransactions,
selectIsFetchingTransactions,
} from 'redux/selectors/wallet';
} from 'lbry-redux';
import TransactionListRecent from './view';
const select = state => ({

View file

@ -1,9 +1,10 @@
import React from 'react';
import { normalizeURI } from 'lbryURI';
import { connect } from 'react-redux';
import { doResolveUri } from 'redux/actions/content';
import { makeSelectIsUriResolving } from 'redux/selectors/content';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import {
normalizeURI,
doResolveUri,
makeSelectIsUriResolving,
makeSelectClaimForUri,
} from 'lbry-redux';
import UriIndicator from './view';
const select = (state, props) => ({

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import Button from 'component/button';
import { buildURI } from 'lbryURI';
import { buildURI } from 'lbry-redux';
import classnames from 'classnames';
// import Icon from 'component/common/icon';

View file

@ -1,19 +1,33 @@
// I'll come back to this
/* eslint-disable */
// @flow
import React from 'react';
import Button from 'component/button';
import { Form, FormField, Submit } from 'component/common/form';
import { Form, FormField, FormRow, Submit } from 'component/common/form';
class UserEmailVerify extends React.PureComponent {
constructor(props) {
type Props = {
cancelButton: React.Node,
errorMessage: ?string,
email: string,
isPending: boolean,
verifyUserEmail: (string, string) => void,
verifyUserEmailFailure: string => void,
};
type State = {
code: string,
};
class UserEmailVerify extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
code: '',
};
(this: any).handleSubmit = this.handleSubmit.bind(this);
}
handleCodeChanged(event) {
handleCodeChanged(event: SyntheticInputEvent<*>) {
this.setState({
code: String(event.target.value).trim(),
});
@ -31,31 +45,30 @@ class UserEmailVerify extends React.PureComponent {
render() {
const { cancelButton, errorMessage, email, isPending } = this.props;
// <FormField
// label={__('Verification Code')}
// errorMessage={errorMessage}
// render{() => (
// <input
// name="code"
// value={this.state.code}
// onChange={event => {
// this.handleCodeChanged(event);
// }}
// />
// )}
// />
return (
<Form onSubmit={this.handleSubmit.bind(this)}>
<Form onSubmit={this.handleSubmit}>
<p>Please enter the verification code emailed to {email}.</p>
{/* render help separately so it always shows */}
<div className="form-field__helper">
<FormRow>
<FormField
stretch
name="code"
type="text"
placeholder="eyJyZWNhcHRjaGEiOiIw..."
label={__('Verification Code')}
error={errorMessage}
value={this.state.code}
onChange={event => this.handleCodeChanged(event)}
/>
</FormRow>
<div className="help">
<p>
{__('Email')} <Button href="mailto:help@lbry.io" label="help@lbry.io" /> or join our{' '}
<Button href="https://chat.lbry.io" label="chat" />{' '}
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '}
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble with your code.')}
</p>
</div>
<div className="form-row-submit">
<div className="card__actions">
<Submit label={__('Verify')} disabled={isPending} />
{cancelButton}
</div>
@ -65,4 +78,3 @@ class UserEmailVerify extends React.PureComponent {
}
export default UserEmailVerify;
/* eslint-enable */

View file

@ -1,7 +1,7 @@
// I'll come back to this
/* eslint-disable */
import React from 'react';
import { Form, FormRow, FormField } from 'component/common/form';
import { Form, FormRow, FormField, Submit } from 'component/common/form';
const os = require('os').type();
const countryCodes = require('country-data')
@ -77,37 +77,30 @@ class UserPhoneNew extends React.PureComponent {
)}
</p>
<Form onSubmit={this.handleSubmit.bind(this)}>
<div className="form-row-phone">
<FormRow>
<FormField type="select" name="country-codes" onChange={this.handleSelect.bind(this)}>
{countryCodes.map((country, index) => (
<option key={index} value={country.countryCallingCode}>
{os === 'Darwin' ? country.emoji : `(${country.alpha2})`}{' '}
{country.countryCallingCode}
</option>
))}
</FormField>
<FormField
onChange={this.handleSelect.bind(this)}
render={() => (
<select>
{countryCodes.map((country, index) => (
<option key={index} value={country.countryCallingCode}>
{os === 'Darwin' ? country.emoji : `(${country.alpha2})`}{' '}
{country.countryCallingCode}
</option>
))}
</select>
)}
/>
<FormField
errorMessage={phoneErrorMessage}
type="text"
placeholder={this.state.country_code === '+1' ? '(555) 555-5555' : '5555555555'}
name="phone"
value={this.state.phone}
error={phoneErrorMessage}
onChange={event => {
this.handleChanged(event);
}}
render={() => (
<input
type="text"
placeholder={this.state.country_code === '+1' ? '(555) 555-5555' : '5555555555'}
name="phone"
value={this.state.phone}
/>
)}
/>
</FormRow>
<div className="card__actions card__actions--center">
<Submit label="Submit" disabled={isPending} />
{cancelButton}
</div>
<Submit label="Submit" disabled={isPending} />
{cancelButton}
</Form>
</div>
);

View file

@ -2,7 +2,7 @@
/* eslint-disable */
import React from 'react';
import Button from 'component/button';
import { Form, FormElement, Submit } from 'component/common/form';
import { Form, FormField, Submit } from 'component/common/form';
class UserPhoneVerify extends React.PureComponent {
constructor(props) {
@ -37,31 +37,27 @@ class UserPhoneVerify extends React.PureComponent {
{__(
`Please enter the verification code sent to +${countryCode}${phone}. Didn't receive it? `
)}
<Button onClick={this.reset.bind(this)} label="Go back." />
<Button button="link" onClick={this.reset.bind(this)} label="Go back." />
</p>
<FormElement
<FormField
type="text"
name="code"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
label={__('Verification Code')}
errorMessage={phoneErrorMessage}
render={() => (
<input
type="text"
name="code"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
/>
)}
error={phoneErrorMessage}
/>
{/* render help separately so it always shows */}
<div className="form-field__helper">
<div className="meta">
<p>
{__('Email')} <Button href="mailto:help@lbry.io" label="help@lbry.io" /> or join our{' '}
<Button href="https://chat.lbry.io" label="chat" />{' '}
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '}
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble with your code.')}
</p>
</div>
<div className="form-row-submit">
<div className="card__actions card__actions--center">
<Submit label={__('Verify')} />
{cancelButton}
</div>

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doUserIdentityVerify } from 'redux/actions/user';
@ -10,7 +9,7 @@ import {
} from 'redux/selectors/user';
import UserVerify from './view';
import { selectCurrentModal } from 'redux/selectors/app';
import { doOpenModal } from 'redux/actions/app';
import { doNotify } from 'lbry-redux';
import { PHONE_COLLECTION } from 'constants/modal_types';
const select = (state, props) => {
@ -27,7 +26,7 @@ const select = (state, props) => {
const perform = dispatch => ({
navigate: uri => dispatch(doNavigate(uri)),
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
verifyPhone: () => dispatch(doOpenModal(PHONE_COLLECTION)),
verifyPhone: () => dispatch(doNotify({ id: PHONE_COLLECTION })),
});
export default connect(select, perform)(UserVerify);

View file

@ -1,31 +1,31 @@
/* eslint-disable */
import React from 'react';
// @flow
import * as React from 'react';
import Button from 'component/button';
import CardVerify from 'component/cardVerify';
import lbryio from 'lbryio.js';
import lbryio from 'lbryio';
import * as icons from 'constants/icons';
class UserVerify extends React.PureComponent {
constructor(props) {
super(props);
type Props = {
errorMessage: ?string,
isPending: boolean,
navigate: string => void,
verifyUserIdentity: string => void,
verifyPhone: () => void,
};
this.state = {
code: '',
};
class UserVerify extends React.PureComponent<Props> {
constructor() {
super();
(this: any).onToken = this.onToken.bind(this);
}
handleCodeChanged(event) {
this.setState({
code: event.target.value,
});
}
onToken(data) {
onToken(data: { id: string }) {
this.props.verifyUserIdentity(data.id);
}
render() {
const { errorMessage, isPending, navigate, verifyPhone, modal } = this.props;
const { errorMessage, isPending, navigate, verifyPhone } = this.props;
return (
<React.Fragment>
<section className="card card--section">
@ -39,20 +39,18 @@ class UserVerify extends React.PureComponent {
</div>
</section>
<section className="card card--section">
<div className="card__title">
<h3>{__('1) Proof via Credit')}</h3>
</div>
<div className="card__content">
<div className="card__title">{__('1) Proof via Credit')}</div>
<p className="card__content">
{`${__(
'If you have a valid credit or debit card, you can use it to instantly prove your humanity.'
)} ${__('There is no charge at all for this, now or in the future.')} `}
</div>
</p>
<div className="card__actions">
{errorMessage && <p className="form-field__error">{errorMessage}</p>}
<CardVerify
label={__('Perform Card Verification')}
disabled={isPending}
token={this.onToken.bind(this)}
token={this.onToken}
stripeKey={lbryio.getStripeToken()}
/>
</div>
@ -60,6 +58,7 @@ class UserVerify extends React.PureComponent {
<div className="meta">
{__('A $1 authorization may temporarily appear with your provider.')}{' '}
<Button
button="link"
href="https://lbry.io/faq/identity-requirements"
label={__('Read more about why we do this.')}
/>
@ -67,20 +66,18 @@ class UserVerify extends React.PureComponent {
</div>
</section>
<section className="card card--section">
<div className="card__title">
<h3>{__('2) Proof via Phone')}</h3>
</div>
<div className="card__content">
<div className="card__title">{__('2) Proof via Phone')}</div>
<p className="card__content">
{`${__(
'You will receive an SMS text message confirming that your phone number is correct.'
)}`}
</div>
</p>
<div className="card__actions">
<Button
onClick={() => {
verifyPhone();
}}
button="alt"
button="primary"
icon={icons.PHONE}
label={__('Submit Phone Number')}
/>
@ -88,14 +85,12 @@ class UserVerify extends React.PureComponent {
<div className="card__content">
<div className="meta">
{__('Standard messaging rates apply. Having trouble?')}{' '}
<Button href="https://lbry.io/faq/phone" label={__('Read more.')} />
<Button button="link" href="https://lbry.io/faq/phone" label={__('Read more.')} />
</div>
</div>
</section>
<section className="card card--form">
<div className="card__title">
<h3>{__('3) Proof via Chat')}</h3>
</div>
<section className="card card--section">
<div className="card__title">{__('3) Proof via Chat')}</div>
<div className="card__content">
<p>
{__(
@ -111,25 +106,25 @@ class UserVerify extends React.PureComponent {
<div className="card__actions">
<Button
href="https://chat.lbry.io"
button="alt"
button="primary"
icon={icons.MESSAGE}
label={__('Join LBRY Chat')}
/>
</div>
</section>
<section className="card card--section">
<div className="card__title">
<h5>{__('Or, Skip It Entirely')}</h5>
</div>
<div className="card__content">
<p className="meta">
{__(
'You can continue without this step, but you will not be eligible to earn rewards.'
)}
</p>
</div>
<div className="card__title">{__('Or, Skip It Entirely')}</div>
<p className="card__content">
{__(
'You can continue without this step, but you will not be eligible to earn rewards.'
)}
</p>
<div className="card__actions">
<Button onClick={() => navigate('/discover')} button="alt" label={__('Skip Rewards')} />
<Button
onClick={() => navigate('/discover')}
button="primary"
label={__('Skip Rewards')}
/>
</div>
</section>
</React.Fragment>
@ -138,4 +133,3 @@ class UserVerify extends React.PureComponent {
}
export default UserVerify;
/* eslint-enable */

View file

@ -1,21 +1,21 @@
import React from 'react';
import { connect } from 'react-redux';
import { doChangeVolume } from 'redux/actions/app';
import { selectVolume } from 'redux/selectors/app';
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
import { doPlay, doPause, savePosition } from 'redux/actions/media';
import { makeSelectMetadataForUri, makeSelectContentTypeForUri } from 'redux/selectors/claims';
import {
makeSelectMetadataForUri,
makeSelectContentTypeForUri,
makeSelectCostInfoForUri,
makeSelectClaimForUri,
makeSelectFileInfoForUri,
makeSelectLoadingForUri,
makeSelectDownloadingForUri,
} from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
} from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings';
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
import Video from './view';
import { selectPlayingUri } from 'redux/selectors/content';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import Video from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import lbry from 'lbry';
import { Lbry } from 'lbry-redux';
import classnames from 'classnames';
import VideoPlayer from './internal/player';
import VideoPlayButton from './internal/play-button';
@ -75,7 +75,7 @@ class Video extends React.PureComponent<Props> {
const isPlaying = playingUri === uri;
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
const mediaType = lbry.getMediaType(contentType, fileInfo && fileInfo.file_name);
const mediaType = Lbry.getMediaType(contentType, fileInfo && fileInfo.file_name);
let loadStatusMessage = '';

View file

@ -1,7 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCheckAddressIsMine, doGetNewAddress } from 'redux/actions/wallet';
import { selectReceiveAddress, selectGettingNewAddress } from 'redux/selectors/wallet';
import {
doCheckAddressIsMine,
doGetNewAddress,
selectReceiveAddress,
selectGettingNewAddress,
} from 'lbry-redux';
import WalletAddress from './view';
const select = state => ({

View file

@ -13,7 +13,12 @@ type Props = {
class WalletAddress extends React.PureComponent<Props> {
componentWillMount() {
this.props.checkAddressIsMine(this.props.receiveAddress);
const { checkAddressIsMine, receiveAddress, getNewAddress } = this.props;
if (!receiveAddress) {
getNewAddress();
} else {
checkAddressIsMine(receiveAddress);
}
}
render() {

View file

@ -1,6 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectBalance } from 'redux/selectors/wallet';
import { selectBalance } from 'lbry-redux';
import WalletBalance from './view';
const select = state => ({

View file

@ -1,6 +1,5 @@
import { connect } from 'react-redux';
import { doSendDraftTransaction } from 'redux/actions/wallet';
import { selectBalance } from 'redux/selectors/wallet';
import { doSendDraftTransaction, selectBalance } from 'lbry-redux';
import WalletSend from './view';
const perform = dispatch => ({

View file

@ -51,6 +51,7 @@ class WalletSend extends React.PureComponent<Props> {
postfix={__('LBC')}
className="input--price-amount"
min="0"
step="any"
onChange={handleChange}
onBlur={handleBlur}
value={values.amount}

View file

@ -1,17 +1,20 @@
import React from 'react';
import { connect } from 'react-redux';
import { doSendSupport } from 'redux/actions/wallet';
import {
doSendSupport,
makeSelectTitleForUri,
makeSelectClaimForUri,
selectIsSendingSupport,
} from 'lbry-redux';
import WalletSendTip from './view';
import { makeSelectTitleForUri } from 'redux/selectors/claims';
import { selectIsSendingSupport } from 'redux/selectors/wallet';
const select = (state, props) => ({
isPending: selectIsSendingSupport(state),
title: makeSelectTitleForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
});
const perform = dispatch => ({
sendSupport: (amount, claim_id, uri) => dispatch(doSendSupport(amount, claim_id, uri)),
sendSupport: (amount, claimId, uri) => dispatch(doSendSupport(amount, claimId, uri)),
});
export default connect(select, perform)(WalletSendTip);

View file

@ -5,9 +5,9 @@ import { FormField } from 'component/common/form';
import UriIndicator from 'component/uriIndicator';
type Props = {
claim_id: string,
uri: string,
title: string,
claim: { claim_id: string },
errorMessage: string,
isPending: boolean,
sendSupport: (number, string, string) => void,
@ -31,7 +31,8 @@ class WalletSendTip extends React.PureComponent<Props, State> {
}
handleSendButtonClicked() {
const { claim_id: claimId, uri, sendSupport, sendTipCallback } = this.props;
const { claim, uri, sendSupport, sendTipCallback } = this.props;
const { claim_id: claimId } = claim;
const { amount } = this.state;
sendSupport(amount, claimId, uri);
@ -49,7 +50,7 @@ class WalletSendTip extends React.PureComponent<Props, State> {
}
render() {
const { errorMessage, isPending, title, uri, onCancel } = this.props;
const { title, errorMessage, isPending, uri, onCancel } = this.props;
return (
<div>

View file

@ -1,10 +1,13 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { normalizeURI } from 'lbryURI';
import { selectState as selectSearch, selectWunderBarAddress } from 'redux/selectors/search';
import { doUpdateSearchQuery } from 'redux/actions/search';
import { normalizeURI } from 'lbry-redux';
import {
selectSearchState as selectSearch,
selectWunderBarAddress,
doUpdateSearchQuery,
doNotify,
} from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doOpenModal } from 'redux/actions/app';
import Wunderbar from './view';
const select = state => {
@ -24,7 +27,7 @@ const select = state => {
const perform = dispatch => ({
onSearch: query => {
dispatch(doUpdateSearchQuery(query));
dispatch(doOpenModal(MODALS.SEARCH));
dispatch(doNotify({ id: MODALS.SEARCH }));
},
onSubmit: (uri, extraParams) => dispatch(doNavigate('/show', { uri, ...extraParams })),
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import { normalizeURI } from 'lbryURI';
import { normalizeURI } from 'lbry-redux';
import Icon from 'component/common/icon';
import { parseQueryParams } from 'util/query_params';
import * as icons from 'constants/icons';

View file

@ -1,7 +1,3 @@
export const OPEN_MODAL = 'OPEN_MODAL';
export const CLOSE_MODAL = 'CLOSE_MODAL';
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR';
export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK';
export const WINDOW_FOCUSED = 'WINDOW_FOCUSED';
export const DAEMON_READY = 'DAEMON_READY';
export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH';

View file

@ -7,12 +7,8 @@ import { ipcRenderer, remote, shell } from 'electron';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import {
doConditionalAuthNavigate,
doDaemonReady,
doShowSnackBar,
doAutoUpdate,
} from 'redux/actions/app';
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate } from 'redux/actions/app';
import { doNotify } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
import { doUserEmailVerify } from 'redux/actions/user';
@ -38,7 +34,12 @@ ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
app.store.dispatch(doConditionalAuthNavigate(newSession));
app.store.dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
} else {
app.store.dispatch(doShowSnackBar({ message: 'Invalid Verification URI' }));
app.store.dispatch(
doNotify({
message: 'Invalid Verification URI',
displayType: ['snackbar'],
})
);
}
} else {
app.store.dispatch(doNavigate('/show', { uri }));

View file

@ -1,86 +0,0 @@
const jsonrpc = {};
jsonrpc.call = (
connectionString,
method,
params,
callback,
errorCallback,
connectFailedCallback
) => {
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
error = new Error(json.error.message);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0, 10);
const url = connectionString;
const options = {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: counter,
}),
};
sessionStorage.setItem('JSONRPCCounter', counter + 1);
return fetch(url, options)
.then(checkAndParse)
.then(response => {
const error = response.error || (response.result && response.result.error);
if (!error && typeof callback === 'function') {
return callback(response.result);
}
if (error && typeof errorCallback === 'function') {
return errorCallback(error);
}
const errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString,
method,
params,
code: error.code,
message: error.message || error,
data: error.data,
},
});
document.dispatchEvent(errorEvent);
return Promise.resolve();
})
.catch(error => {
if (connectFailedCallback) {
return connectFailedCallback(error);
}
const errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString,
method,
params,
code: error.response && error.response.status,
message: __('Connection to API server failed'),
},
});
document.dispatchEvent(errorEvent);
return Promise.resolve();
});
};
export default jsonrpc;

View file

@ -1,151 +0,0 @@
import { ipcRenderer } from 'electron';
import jsonrpc from 'jsonrpc';
const CHECK_DAEMON_STARTED_TRY_NUMBER = 200;
const Lbry = {
isConnected: false,
daemonConnectionString: 'http://localhost:5279',
pendingPublishTimeout: 20 * 60 * 1000,
};
function apiCall(method, params, resolve, reject) {
return jsonrpc.call(Lbry.daemonConnectionString, method, params, resolve, reject, reject);
}
const lbryProxy = new Proxy(Lbry, {
get(target, name) {
if (name in target) {
return target[name];
}
return (params = {}) =>
new Promise((resolve, reject) => {
apiCall(name, params, resolve, reject);
});
},
});
function getLocal(key, fallback = undefined) {
const itemRaw = localStorage.getItem(key);
return itemRaw === null ? fallback : JSON.parse(itemRaw);
}
function setLocal(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// core
Lbry.connectPromise = null;
Lbry.connect = () => {
if (Lbry.connectPromise === null) {
Lbry.connectPromise = new Promise((resolve, reject) => {
let tryNum = 0;
// Check every half second to see if the daemon is accepting connections
function checkDaemonStarted() {
tryNum += 1;
lbryProxy
.status()
.then(resolve)
.catch(() => {
if (tryNum <= CHECK_DAEMON_STARTED_TRY_NUMBER) {
setTimeout(checkDaemonStarted, tryNum < 50 ? 400 : 1000);
} else {
reject(new Error('Unable to connect to LBRY'));
}
});
}
checkDaemonStarted();
});
}
return Lbry.connectPromise;
};
Lbry.imagePath = file => `${staticResourcesPath}/img/${file}`;
Lbry.getAppVersionInfo = () =>
new Promise(resolve => {
ipcRenderer.once('version-info-received', (event, versionInfo) => {
resolve(versionInfo);
});
ipcRenderer.send('version-info-requested');
});
Lbry.getMediaType = (contentType, fileName) => {
if (contentType) {
return /^[^/]+/.exec(contentType)[0];
} else if (fileName) {
const dotIndex = fileName.lastIndexOf('.');
if (dotIndex === -1) {
return 'unknown';
}
const ext = fileName.substr(dotIndex + 1);
if (/^mp4|m4v|webm|flv|f4v|ogv$/i.test(ext)) {
return 'video';
} else if (/^mp3|m4a|aac|wav|flac|ogg|opus$/i.test(ext)) {
return 'audio';
} else if (/^html|htm|xml|pdf|odf|doc|docx|md|markdown|txt|epub|org$/i.test(ext)) {
return 'document';
}
return 'unknown';
}
return 'unknown';
};
/**
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
* these are designed to be transparent wrappers around the corresponding API methods.
*/
/**
* Returns results from the file_list API method, plus dummy entries for pending publishes.
* (If a real publish with the same claim name is found, the pending publish will be ignored and removed.)
*/
Lbry.file_list = (params = {}) =>
new Promise((resolve, reject) => {
const { claim_name: claimName, channel_name: channelName, outpoint } = params;
apiCall(
'file_list',
params,
fileInfos => {
resolve(fileInfos);
},
reject
);
});
Lbry.claim_list_mine = (params = {}) =>
new Promise((resolve, reject) => {
apiCall(
'claim_list_mine',
params,
claims => {
resolve(claims);
},
reject
);
});
Lbry.resolve = (params = {}) =>
new Promise((resolve, reject) => {
apiCall(
'resolve',
params,
data => {
if ('uri' in params) {
// If only a single URI was requested, don't nest the results in an object
resolve(data && data[params.uri] ? data[params.uri] : {});
} else {
resolve(data || {});
}
},
reject
);
});
export default lbryProxy;

View file

@ -1,230 +0,0 @@
const channelNameMinLength = 1;
const claimIdMaxLength = 40;
export const regexInvalidURI = /[^A-Za-z0-9-]/g;
export const regexAddress = /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
/**
* Parses a LBRY name into its component parts. Throws errors with user-friendly
* messages for invalid names.
*
* N.B. that "name" indicates the value in the name position of the URI. For
* claims for channel content, this will actually be the channel name, and
* the content name is in the path (e.g. lbry://@channel/content)
*
* In most situations, you'll want to use the contentName and channelName keys
* and ignore the name key.
*
* Returns a dictionary with keys:
* - name (string): The value in the "name" position in the URI. Note that this
* could be either content name or channel name; see above.
* - path (string, if persent)
* - claimSequence (int, if present)
* - bidPosition (int, if present)
* - claimId (string, if present)
* - isChannel (boolean)
* - contentName (string): For anon claims, the name; for channel claims, the path
* - channelName (string, if present): Channel name without @
*/
export function parseURI(URI, requireProto = false) {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp(
'^((?:lbry://)?)' + // protocol
'([^:$#/]*)' + // claim name (stops at the first separator or end)
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
'(/?)(.*)' // path separator, path
);
const [proto, claimName, modSep, modVal, pathSep, path] = componentsRegex
.exec(URI)
.slice(1)
.map(match => match || null);
let contentName;
// Validate protocol
if (requireProto && !proto) {
throw new Error(__('LBRY URIs must include a protocol prefix (lbry://).'));
}
// Validate and process name
if (!claimName) {
throw new Error(__('URI does not include name.'));
}
const isChannel = claimName.startsWith('@');
const channelName = isChannel ? claimName.slice(1) : claimName;
if (isChannel) {
if (!channelName) {
throw new Error(__('No channel name after @.'));
}
if (channelName.length < channelNameMinLength) {
throw new Error(__(`Channel names must be at least %s characters.`, channelNameMinLength));
}
contentName = path;
}
const nameBadChars = (channelName || claimName).match(regexInvalidURI);
if (nameBadChars) {
throw new Error(
__(
`Invalid character %s in name: %s.`,
nameBadChars.length === 1 ? '' : 's',
nameBadChars.join(', ')
)
);
}
// Validate and process modifier (claim ID, bid position or claim sequence)
let claimId;
let claimSequence;
let bidPosition;
if (modSep) {
if (!modVal) {
throw new Error(__(`No modifier provided after separator %s.`, modSep));
}
if (modSep === '#') {
claimId = modVal;
} else if (modSep === ':') {
claimSequence = modVal;
} else if (modSep === '$') {
bidPosition = modVal;
}
}
if (
claimId &&
(claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/)) &&
!claimId.match(/^pending/) // ought to be dropped when savePendingPublish drops hack
) {
throw new Error(__(`Invalid claim ID %s.`, claimId));
}
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
throw new Error(__('Claim sequence must be a number.'));
}
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
throw new Error(__('Bid position must be a number.'));
}
// Validate and process path
if (path) {
if (!isChannel) {
throw new Error(__('Only channel URIs may have a path.'));
}
const pathBadChars = path.match(regexInvalidURI);
if (pathBadChars) {
throw new Error(__(`Invalid character in path: %s`, pathBadChars.join(', ')));
}
contentName = path;
} else if (pathSep) {
throw new Error(__('No path provided after /'));
}
return {
claimName,
path,
isChannel,
...(contentName ? { contentName } : {}),
...(channelName ? { channelName } : {}),
...(claimSequence ? { claimSequence: parseInt(claimSequence, 10) } : {}),
...(bidPosition ? { bidPosition: parseInt(bidPosition, 10) } : {}),
...(claimId ? { claimId } : {}),
...(path ? { path } : {}),
};
}
/**
* Takes an object in the same format returned by parse() and builds a URI.
*
* The channelName key will accept names with or without the @ prefix.
*/
export function buildURI(URIObj, includeProto = true) {
const { claimId, claimSequence, bidPosition, contentName, channelName } = URIObj;
let { claimName, path } = URIObj;
if (channelName) {
const channelNameFormatted = channelName.startsWith('@') ? channelName : `@${channelName}`;
if (!claimName) {
claimName = channelNameFormatted;
} else if (claimName !== channelNameFormatted) {
throw new Error(
__(
'Received a channel content URI, but claim name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'
)
);
}
}
if (contentName) {
if (!claimName) {
claimName = contentName;
} else if (!path) {
path = contentName;
}
if (path && path !== contentName) {
throw new Error(
__(
'Path and contentName do not match. Only one is required; most likely you wanted contentName.'
)
);
}
}
return (
(includeProto ? 'lbry://' : '') +
claimName +
(claimId ? `#${claimId}` : '') +
(claimSequence ? `:${claimSequence}` : '') +
(bidPosition ? `${bidPosition}` : '') +
(path ? `/${path}` : '')
);
}
/* Takes a parseable LBRY URI and converts it to standard, canonical format */
export function normalizeURI(URI) {
if (URI.match(/pending_claim/)) return URI;
const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI);
return buildURI({ claimName, path, claimSequence, bidPosition, claimId });
}
export function isURIValid(URI) {
let parts;
try {
parts = parseURI(normalizeURI(URI));
} catch (error) {
return false;
}
return parts && parts.claimName;
}
export function isNameValid(claimName, checkCase = true) {
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
return regexp.test(claimName);
}
export function isURIClaimable(URI) {
let parts;
try {
parts = parseURI(normalizeURI(URI));
} catch (error) {
return false;
}
return (
parts &&
parts.claimName &&
!parts.claimId &&
!parts.bidPosition &&
!parts.claimSequence &&
!parts.isChannel &&
!parts.path
);
}

View file

@ -1,38 +1,16 @@
import { ipcRenderer } from 'electron';
import Lbry from 'lbry';
import { Lbry } from 'lbry-redux';
import querystring from 'querystring';
const Lbryio = {
enabled: true,
authenticationPromise: null,
exchangePromise: null,
exchangeLastFetched: null,
};
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
? process.env.LBRY_APP_API_URL.replace(/\/*$/, '/') // exactly one slash at the end
: 'https://api.lbry.io/';
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
Lbryio.getExchangeRates = () => {
if (
!Lbryio.exchangeLastFetched ||
Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
) {
Lbryio.exchangePromise = new Promise((resolve, reject) => {
Lbryio.call('lbc', 'exchange_rate', {}, 'get', true)
.then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => {
const rates = { LBC_USD, LBC_BTC, BTC_USD };
resolve(rates);
})
.catch(reject);
});
Lbryio.exchangeLastFetched = Date.now();
}
return Lbryio.exchangePromise;
};
Lbryio.call = (resource, action, params = {}, method = 'get') => {
if (!Lbryio.enabled) {
console.log(__('Internal API disabled'));

View file

@ -1,8 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doLoadVideo, doSetPlayingUri } from 'redux/actions/content';
import { makeSelectMetadataForUri } from 'redux/selectors/claims';
import { doHideNotification, makeSelectMetadataForUri } from 'lbry-redux';
import ModalAffirmPurchase from './view';
const select = (state, props) => ({
@ -12,9 +10,9 @@ const select = (state, props) => ({
const perform = dispatch => ({
cancelPurchase: () => {
dispatch(doSetPlayingUri(null));
dispatch(doCloseModal());
dispatch(doHideNotification());
},
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
loadVideo: uri => dispatch(doLoadVideo(uri)),
});

View file

@ -1,12 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import ModalAuthFailure from './view';
const select = state => ({});
const perform = dispatch => ({
close: () => dispatch(doCloseModal()),
close: () => dispatch(doHideNotification()),
});
export default connect(null, null)(ModalAuthFailure);

View file

@ -1,10 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal, doAutoUpdateDeclined } from 'redux/actions/app';
import { doAutoUpdateDeclined } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import ModalAutoUpdateConfirm from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
});

View file

@ -1,10 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal, doAutoUpdateDeclined } from 'redux/actions/app';
import { doAutoUpdateDeclined } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import ModalAutoUpdateDownloaded from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
declineAutoUpdate: () => dispatch(doAutoUpdateDeclined()),
});

View file

@ -1,10 +1,8 @@
import React from 'react';
import { ipcRenderer } from 'electron';
import { Modal } from 'modal/modal';
import { Line } from 'rc-progress';
import Button from 'component/button';
const { ipcRenderer } = require('electron');
class ModalAutoUpdateDownloaded extends React.PureComponent {
render() {
const { closeModal, declineAutoUpdate } = this.props;
@ -32,6 +30,14 @@ class ModalAutoUpdateDownloaded extends React.PureComponent {
'A new version of LBRY has been released, downloaded, and is ready for you to use pending a restart.'
)}
</p>
<p className="meta text-center">
{__('Want to know what has changed?')} See the{' '}
<Button
button="link"
label={__('release notes')}
href="https://github.com/lbryio/lbry-app/releases"
/>.
</p>
</section>
</Modal>
);

View file

@ -1,9 +1,8 @@
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doNavigate } from 'redux/actions/navigation';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectUserIsRewardApproved } from 'redux/selectors/user';
import { selectBalance } from 'redux/selectors/wallet';
import { selectBalance, doHideNotification } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
import * as settings from 'constants/settings';
import ModalCreditIntro from './view';
@ -18,11 +17,11 @@ const perform = dispatch => () => ({
addBalance: () => {
dispatch(doSetClientSetting(settings.CREDIT_REQUIRED_ACKNOWLEDGED, true));
dispatch(doNavigate('/getcredits'));
dispatch(doCloseModal());
dispatch(doHideNotification());
},
closeModal: () => {
dispatch(doSetClientSetting(settings.CREDIT_REQUIRED_ACKNOWLEDGED, true));
dispatch(doCloseModal());
dispatch(doHideNotification());
},
});

View file

@ -21,24 +21,24 @@ const ModalCreditIntro = props => {
</p>
{currentBalance <= 0 && (
<p>
You currently have <CreditAmount amount={currentBalance} />, so the actions you can take
are limited.
You currently have <CreditAmount noStyle amount={currentBalance} />, so the actions you
can take are limited.
</p>
)}
<p>
There are a variety of ways to get credits, including more than{' '}
{totalRewardValue ? (
<CreditAmount amount={totalRewardRounded} />
<CreditAmount noStyle amount={totalRewardRounded} />
) : (
<span className="credit-amount">{__('?? credits')}</span>
)}{' '}
{__(' in free rewards for participating in the LBRY beta.')}
</p>
<div className="modal__buttons">
<div className="card__actions card__actions--center">
<Button button="primary" onClick={addBalance} label={__('Get Credits')} />
<Button
button="alt"
button="link"
onClick={closeModal}
label={currentBalance <= 0 ? __('Use Without LBC') : __('Meh, Not Now')}
/>

View file

@ -1,7 +1,6 @@
import React from 'react';
import * as settings from 'constants/settings';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
import ModalEmailCollection from './view';
@ -14,7 +13,7 @@ const select = state => ({
const perform = dispatch => () => ({
closeModal: () => {
dispatch(doSetClientSetting(settings.EMAIL_COLLECTION_ACKNOWLEDGED, true));
dispatch(doCloseModal());
dispatch(doHideNotification());
},
});

View file

@ -1,10 +1,9 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import ModalError from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
});
export default connect(null, perform)(ModalError);

View file

@ -1,5 +1,5 @@
import React from 'react';
import lbry from 'lbry';
import Native from 'native';
import { ExpandableModal } from 'modal/modal';
class ModalError extends React.PureComponent {
@ -8,7 +8,7 @@ class ModalError extends React.PureComponent {
const errorObj = typeof error === 'string' ? { message: error } : error;
const error_key_labels = {
const errorKeyLabels = {
connectionString: __('API connection string'),
method: __('Method'),
params: __('Parameters'),
@ -20,7 +20,7 @@ class ModalError extends React.PureComponent {
const errorInfoList = [];
for (const key of Object.keys(errorObj)) {
const val = typeof errorObj[key] === 'string' ? errorObj[key] : JSON.stringify(errorObj[key]);
const label = error_key_labels[key];
const label = errorKeyLabels[key];
errorInfoList.push(
<li key={key}>
<strong>{label}</strong>: <code>{val}</code>
@ -42,7 +42,11 @@ class ModalError extends React.PureComponent {
<div className="error-modal__content">
<div>
<img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} />
<img
alt=""
className="error-modal__warning-symbol"
src={Native.imagePath('warning.png')}
/>
</div>
<p>
{__(

View file

@ -1,15 +1,13 @@
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { makeSelectMetadataForUri } from 'redux/selectors/claims';
import { doHideNotification, makeSelectMetadataForUri } from 'lbry-redux';
import ModalFileTimeout from './view';
const select = state => ({
const select = (state, props) => ({
metadata: makeSelectMetadataForUri(props.uri)(state),
});
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
});
export default connect(select, perform)(ModalFileTimeout);

View file

@ -1,11 +1,10 @@
import React from 'react';
import rewards from 'rewards';
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import { makeSelectRewardByType } from 'redux/selectors/rewards';
import ModalFirstReward from './view';
const select = (state, props) => {
const select = state => {
const selectReward = makeSelectRewardByType();
return {
@ -14,7 +13,7 @@ const select = (state, props) => {
};
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
});
export default connect(select, perform)(ModalFirstReward);

View file

@ -1,10 +1,10 @@
import { connect } from 'react-redux';
import { doCloseModal } from 'redux/actions/app';
import { doHideNotification } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation';
import ModalFirstSubscription from './view';
const perform = dispatch => () => ({
closeModal: () => dispatch(doCloseModal()),
closeModal: () => dispatch(doHideNotification()),
navigate: path => dispatch(doNavigate(path)),
});

Some files were not shown because too many files have changed in this diff Show more