lbry-desktop/src/renderer/component/publishForm/view.jsx

567 lines
18 KiB
React
Raw Normal View History

2018-03-26 23:32:43 +02:00
// @flow
import * as React from 'react';
2018-03-30 07:37:09 +02:00
import { isNameValid, buildURI, regexInvalidURI } from 'lbryURI';
2018-03-26 23:32:43 +02:00
import { Form, FormField, FormRow, FormFieldPrice, Submit } from 'component/common/form';
import Button from 'component/button';
import ChannelSection from 'component/selectChannel';
import classnames from 'classnames';
import type { PublishParams, UpdatePublishFormData } from 'redux/reducers/publish';
import FileSelector from 'component/common/file-selector';
import { COPYRIGHT, OTHER } from 'constants/licenses';
2018-03-30 07:37:09 +02:00
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID } from 'constants/claim';
2018-03-26 23:32:43 +02:00
import * as icons from 'constants/icons';
2018-03-30 07:37:09 +02:00
import BidHelpText from './internal/bid-help-text';
import LicenseType from './internal/license-type';
2018-03-26 23:32:43 +02:00
type Props = {
publish: PublishParams => void,
filePath: ?string,
bid: ?number,
editingURI: ?string,
title: ?string,
thumbnail: ?string,
description: ?string,
language: string,
nsfw: boolean,
contentIsFree: boolean,
price: {
amount: number,
currency: string,
},
channel: string,
name: ?string,
tosAccepted: boolean,
updatePublishForm: UpdatePublishFormData => void,
bid: number,
nameError: ?string,
isResolvingUri: boolean,
winningBidForClaimUri: number,
myClaimForUri: ?{
amount: number,
2018-04-04 01:46:03 +02:00
value: {
stream: {
source: { source: string },
},
},
2018-03-26 23:32:43 +02:00
},
licenseType: string,
otherLicenseDescription: ?string,
licenseUrl: ?string,
copyrightNotice: ?string,
uri: ?string,
bidError: ?string,
publishing: boolean,
balance: number,
clearPublish: () => void,
resolveUri: string => void,
scrollToTop: () => void,
prepareEdit: ({}) => void,
};
class PublishForm extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
2018-03-26 23:32:43 +02:00
(this: any).handleFileChange = this.handleFileChange.bind(this);
(this: any).checkIsFormValid = this.checkIsFormValid.bind(this);
(this: any).renderFormErrors = this.renderFormErrors.bind(this);
(this: any).handlePublish = this.handlePublish.bind(this);
(this: any).handleCancelPublish = this.handleCancelPublish.bind(this);
(this: any).handleNameChange = this.handleNameChange.bind(this);
(this: any).handleChannelChange = this.handleChannelChange.bind(this);
(this: any).editExistingClaim = this.editExistingClaim.bind(this);
(this: any).getNewUri = this.getNewUri.bind(this);
}
// 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;
2018-03-26 23:32:43 +02:00
let uri;
try {
uri = buildURI({ contentName: name, channelName });
} catch (e) {
// something wrong with channel or name
}
if (uri) {
resolveUri(uri);
return uri;
2017-08-26 04:09:56 +02:00
}
return '';
2017-08-26 04:09:56 +02:00
}
2018-03-26 23:32:43 +02:00
handleFileChange(filePath: string, fileName: string) {
const { updatePublishForm, channel, name } = this.props;
const newFileParams: {
filePath: string,
name?: string,
uri?: string,
} = { filePath };
if (!name) {
const parsedFileName = fileName.replace(regexInvalidURI, '');
const uri = this.getNewUri(parsedFileName, channel);
newFileParams.name = parsedFileName;
newFileParams.uri = uri;
2018-03-26 23:32:43 +02:00
}
updatePublishForm(newFileParams);
}
2018-03-26 23:32:43 +02:00
handleNameChange(name: ?string) {
const { channel, updatePublishForm } = this.props;
2018-03-26 23:32:43 +02:00
if (!name) {
updatePublishForm({ name, nameError: undefined });
return;
}
2018-03-26 23:32:43 +02:00
if (!isNameValid(name, false)) {
updatePublishForm({
name,
nameError: __('LBRY names must contain only letters, numbers and dashes.'),
});
return;
}
2018-03-26 23:32:43 +02:00
const uri = this.getNewUri(name, channel);
updatePublishForm({
name,
uri,
2018-03-26 23:32:43 +02:00
nameError: undefined,
});
}
2018-03-26 23:32:43 +02:00
handleChannelChange(channelName: string) {
const { name, updatePublishForm } = this.props;
if (name) {
const uri = this.getNewUri(name, channelName);
updatePublishForm({ channel: channelName, uri });
} else {
2018-03-26 23:32:43 +02:00
updatePublishForm({ channel: channelName });
}
}
2018-03-26 23:32:43 +02:00
handleBidChange(bid: number) {
const { balance, updatePublishForm } = this.props;
2018-03-26 23:32:43 +02:00
let bidError;
if (balance <= bid) {
bidError = __('Not enough credits');
} else if (bid <= MINIMUM_PUBLISH_BID) {
bidError = __('Your bid must be higher');
}
2018-03-26 23:32:43 +02:00
updatePublishForm({ bid, bidError });
}
editExistingClaim() {
const { myClaimForUri, prepareEdit, scrollToTop } = this.props;
if (myClaimForUri) {
prepareEdit(myClaimForUri);
scrollToTop();
}
}
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,
2018-04-04 01:46:03 +02:00
myClaimForUri,
} = this.props;
let publishingLicense;
switch (licenseType) {
case COPYRIGHT:
publishingLicense = copyrightNotice;
break;
case OTHER:
publishingLicense = otherLicenseDescription;
break;
default:
publishingLicense = licenseType;
}
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,
};
2018-04-04 01:46:03 +02:00
// Editing a claim
if (!filePath && myClaimForUri) {
const { source } = myClaimForUri.value.stream;
publishParams.sources = source;
2018-04-04 01:46:03 +02:00
}
publish(publishParams);
}
2018-03-26 23:32:43 +02:00
checkIsFormValid() {
const { name, nameError, title, bid, bidError, tosAccepted } = this.props;
return name && !nameError && title && bid && !bidError && tosAccepted;
}
2018-03-26 23:32:43 +02:00
renderFormErrors() {
const { name, nameError, title, bid, bidError, tosAccepted } = this.props;
2018-03-26 23:32:43 +02:00
if (nameError || bidError) {
// There will be inline errors if either of these exist
// These are just extra help at the bottom of the screen
// There could be multiple bid errors, so just duplicate it at the bottom
return (
2018-03-26 23:32:43 +02:00
<div className="card__subtitle form-field__error">
{nameError && <div>{__('The URL you created is not valid.')}</div>}
{bidError && <div>{bidError}</div>}
</div>
);
}
2018-03-26 23:32:43 +02:00
return (
<div className="card__content card__subtitle card__subtitle--block form-field__error">
{!title && <div>{__('A title is required')}</div>}
{!name && <div>{__('A URL is required')}</div>}
{!bid && <div>{__('A bid amount is required')}</div>}
{!tosAccepted && <div>{__('You must agree to the terms of service')}</div>}
</div>
);
}
render() {
2018-03-26 23:32:43 +02:00
const {
filePath,
editingURI,
title,
thumbnail,
description,
language,
nsfw,
contentIsFree,
price,
channel,
name,
tosAccepted,
updatePublishForm,
bid,
nameError,
isResolvingUri,
winningBidForClaimUri,
myClaimForUri,
licenseType,
otherLicenseDescription,
licenseUrl,
copyrightNotice,
uri,
bidError,
publishing,
clearPublish,
} = this.props;
const formDisabled = (!filePath && !editingURI) || publishing;
const formValid = this.checkIsFormValid();
const isStillEditing = editingURI === uri;
let submitLabel;
if (isStillEditing) {
submitLabel = !publishing ? __('Edit') : __('Editing...');
} else {
submitLabel = !publishing ? __('Publish') : __('Publishing...');
2017-09-05 03:03:48 +02:00
}
return (
2018-03-26 23:32:43 +02:00
<Form onSubmit={this.handlePublish}>
<section className={classnames('card card--section')}>
<div className="card__title">{__('Content')}</div>
<div className="card__subtitle">
{editingURI ? __('Editing a claim') : __('What are you publishing?')}
</div>
{(filePath || !!editingURI) && (
<div className="card-media__internal-links">
<Button
button="inverse"
icon={icons.CLOSE}
label={__('Clear')}
onClick={clearPublish}
/>
</div>
2018-03-26 23:32:43 +02:00
)}
<FileSelector currentPath={filePath} onFileChosen={this.handleFileChange} />
{!!editingURI && (
<p className="card__content card__subtitle">
{__("If you don't choose a file, the file from your existing claim")}
{` "${name}" `}
{__('will be used.')}
</p>
)}
</section>
<div className={classnames({ 'card--disabled': formDisabled })}>
<section className="card card--section">
<FormRow>
<FormField
stretch
type="text"
name="content_title"
label={__('Title')}
placeholder={__('Titular Title')}
disabled={formDisabled}
value={title}
onChange={e => updatePublishForm({ title: e.target.value })}
/>
</FormRow>
<FormRow padded>
<FormField
stretch
type="text"
name="content_thumbnail"
label={__('Thumbnail')}
placeholder="http://spee.ch/mylogo"
value={thumbnail}
disabled={formDisabled}
onChange={e => updatePublishForm({ thumbnail: e.target.value })}
/>
</FormRow>
<FormRow padded>
<FormField
stretch
type="markdown"
name="content_description"
label={__('Description')}
placeholder={__('Description of your content')}
value={description}
disabled={formDisabled}
onChange={text => updatePublishForm({ description: text })}
/>
</FormRow>
</section>
2018-03-26 23:32:43 +02:00
<section className="card card--section">
<div className="card__title">{__('Price')}</div>
<div className="card__subtitle">{__('How much will this content cost?')}</div>
<div className="card__content">
2018-03-26 23:32:43 +02:00
<FormField
type="radio"
2018-03-26 23:32:43 +02:00
name="content_free"
postfix={__('Free')}
checked={contentIsFree}
disabled={formDisabled}
onChange={() => updatePublishForm({ contentIsFree: true })}
/>
<FormField
type="radio"
2018-03-26 23:32:43 +02:00
name="content_cost"
postfix={__('Choose price')}
checked={!contentIsFree}
disabled={formDisabled}
onChange={() => updatePublishForm({ contentIsFree: false })}
/>
2018-03-26 23:32:43 +02:00
<FormFieldPrice
name="content_cost_amount"
min="0"
price={price}
onChange={newPrice => updatePublishForm({ price: newPrice })}
disabled={formDisabled || contentIsFree}
/>
{price.currency !== 'LBC' && (
<p className="form-field__help">
2017-11-21 20:51:12 +01:00
{__(
'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
2017-11-21 20:51:12 +01:00
)}
2018-03-26 23:32:43 +02:00
</p>
)}
2017-10-14 21:41:04 +02:00
</div>
</section>
2018-03-26 23:32:43 +02:00
<section className="card card--section">
<div className="card__title">{__('Anonymous or under a channel?')}</div>
<p className="card__subtitle">
{__('This is a username or handle that your content can be found under.')}{' '}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
</p>
<ChannelSection channel={channel} onChannelChange={this.handleChannelChange} />
</section>
2017-11-21 20:51:12 +01:00
2018-03-26 23:32:43 +02:00
<section className="card card--section">
<div className="card__title">{__('Where can people find this content?')}</div>
<p className="card__subtitle">
{__(
'The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).'
)}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.io/faq/naming" />
</p>
<div className="card__content">
<FormRow>
<FormField
stretch
prefix={`lbry://${
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW
? ''
: `${channel}/`
2018-03-26 23:32:43 +02:00
}`}
2017-11-21 20:51:12 +01:00
type="text"
2018-03-26 23:32:43 +02:00
name="content_name"
placeholder="myname"
value={name}
onChange={event => this.handleNameChange(event.target.value)}
error={nameError}
helper={
<BidHelpText
uri={uri}
editingURI={editingURI}
isResolvingUri={isResolvingUri}
winningBidForClaimUri={winningBidForClaimUri}
claimIsMine={!!myClaimForUri}
onEditMyClaim={this.editExistingClaim}
/>
}
2017-11-21 20:51:12 +01:00
/>
2018-03-26 23:32:43 +02:00
</FormRow>
</div>
<div className={classnames('card__content', { 'card--disabled': !name })}>
<FormField
className="input--price-amount"
type="number"
name="content_bid"
step="any"
label={__('Deposit')}
postfix="LBC"
value={bid}
error={bidError}
min="0"
disabled={!name}
onChange={event => this.handleBidChange(parseFloat(event.target.value))}
helper={__('This LBC remains yours and the deposit can be undone at any time.')}
placeholder={winningBidForClaimUri ? winningBidForClaimUri + 0.1 : 0.1}
/>
</div>
</section>
2018-03-26 23:32:43 +02:00
<section className="card card--section">
<FormRow>
<FormField
type="checkbox"
name="content_is_mature"
postfix={__('Mature audiences only')}
checked={nsfw}
onChange={event => updatePublishForm({ nsfw: event.target.checked })}
/>
2018-03-26 23:32:43 +02:00
</FormRow>
<FormRow padded>
<FormField
label={__('Language')}
type="select"
name="content_language"
value={language}
onChange={event => updatePublishForm({ language: event.target.value })}
>
<option value="en">{__('English')}</option>
<option value="zh">{__('Chinese')}</option>
<option value="fr">{__('French')}</option>
<option value="de">{__('German')}</option>
<option value="jp">{__('Japanese')}</option>
<option value="ru">{__('Russian')}</option>
<option value="es">{__('Spanish')}</option>
</FormField>
</FormRow>
<LicenseType
licenseType={licenseType}
otherLicenseDescription={otherLicenseDescription}
licenseUrl={licenseUrl}
copyrightNotice={copyrightNotice}
handleLicenseChange={(newLicenseType, newLicenseUrl) =>
updatePublishForm({
licenseType: newLicenseType,
licenseUrl: newLicenseUrl,
})
}
handleLicenseDescriptionChange={event =>
updatePublishForm({
otherLicenseDescription: event.target.value,
})
}
handleLicenseUrlChange={event =>
updatePublishForm({ licenseUrl: event.target.value })
}
handleCopyrightNoticeChange={event =>
updatePublishForm({ copyrightNotice: event.target.value })
}
/>
</section>
2018-03-26 23:32:43 +02:00
<section className="card card--section">
<div className="card__title">{__('Terms of Service')}</div>
<div className="card__content">
2018-03-26 23:32:43 +02:00
<FormField
name="lbry_tos"
type="checkbox"
checked={tosAccepted}
postfix={
<span>
{__('I agree to the')}{' '}
2018-03-26 23:32:43 +02:00
<Button
button="link"
href="https://www.lbry.io/termsofservice"
label={__('LBRY terms of service')}
/>
</span>
}
2018-03-26 23:32:43 +02:00
onChange={event => updatePublishForm({ tosAccepted: event.target.checked })}
/>
</div>
</section>
2018-03-26 23:32:43 +02:00
<div className="card__actions">
<Submit label={submitLabel} disabled={formDisabled || !formValid || publishing} />
<Button button="alt" onClick={this.handleCancelPublish} label={__('Cancel')} />
</div>
2018-03-26 23:32:43 +02:00
{!formDisabled && !formValid && this.renderFormErrors()}
</div>
</Form>
);
}
}
export default PublishForm;