// @flow import * as React from 'react'; import { isNameValid, buildURI, regexInvalidURI, THUMBNAIL_STATUSES } from 'lbry-redux'; 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 SelectThumbnail from 'component/selectThumbnail'; import { COPYRIGHT, OTHER } from 'constants/licenses'; import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID } from 'constants/claim'; import * as icons from 'constants/icons'; import type { Claim } from 'types/claim'; import BidHelpText from './internal/bid-help-text'; import LicenseType from './internal/license-type'; type Props = { publish: PublishParams => void, filePath: ?string, bid: ?number, editingURI: ?string, title: ?string, thumbnail: ?string, uploadThumbnailStatus: ?string, thumbnailPath: ?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: ?Claim, licenseType: string, otherLicenseDescription: ?string, licenseUrl: ?string, copyrightNotice: ?string, uri: ?string, bidError: ?string, publishing: boolean, balance: number, isStillEditing: boolean, clearPublish: () => void, resolveUri: string => void, scrollToTop: () => void, prepareEdit: ({ }) => void, resetThumbnailStatus: () => void, }; class PublishForm extends React.PureComponent { constructor(props: Props) { super(props); (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); } componentWillMount() { const { thumbnail } = this.props; if (!thumbnail) { this.props.resetThumbnailStatus(); } } 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 uri; try { uri = buildURI({ contentName: name, channelName }); } catch (e) { // something wrong with channel or name } if (uri) { resolveUri(uri); return uri; } return ''; } 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; } updatePublishForm(newFileParams); } handleNameChange(name: ?string) { const { channel, updatePublishForm } = this.props; if (!name) { updatePublishForm({ name, nameError: undefined }); return; } if (!isNameValid(name, false)) { updatePublishForm({ name, nameError: __('LBRY names must contain only letters, numbers and dashes.'), }); return; } const uri = this.getNewUri(name, channel); updatePublishForm({ name, uri, nameError: undefined, }); } handleChannelChange(channelName: string) { const { name, updatePublishForm } = this.props; const form = { channel: channelName }; if (name) { form.uri = this.getNewUri(name, channelName); } updatePublishForm(form); } handleBidChange(bid: number) { const { balance, updatePublishForm, myClaimForUri } = this.props; let previousBidAmount = 0; if (myClaimForUri) { previousBidAmount = myClaimForUri.amount; } const totalAvailableBidAmount = previousBidAmount + balance; let bidError; if (bid === 0) { bidError = __('Deposit cannot be 0'); } else if (totalAvailableBidAmount === bid) { bidError = __('Please decrease your deposit to account for transaction fees'); } else if (totalAvailableBidAmount < bid) { bidError = __('Deposit cannot be higher than your balance'); } else if (bid <= MINIMUM_PUBLISH_BID) { bidError = __('Your deposit must be higher'); } updatePublishForm({ bid, bidError }); } editExistingClaim(myClaimForUri: ?{}, uri: string) { const { prepareEdit, scrollToTop } = this.props; if (myClaimForUri) { prepareEdit(myClaimForUri, uri); scrollToTop(); } } handleCancelPublish() { const { clearPublish, scrollToTop } = this.props; scrollToTop(); clearPublish(); } handlePublish() { const { filePath, copyrightNotice, licenseType, licenseUrl, otherLicenseDescription, myClaimForUri, publish, } = 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: this.props.bid, title: this.props.title, thumbnail: this.props.thumbnail, description: this.props.description, language: this.props.language, nsfw: this.props.nsfw, license: publishingLicense, licenseUrl: publishingLicenseUrl, otherLicenseDescription, copyrightNotice, name: this.props.name, contentIsFree: this.props.contentIsFree, price: this.props.price, uri: this.props.uri, channel: this.props.channel, isStillEditing: this.props.isStillEditing, }; // Editing a claim if (!filePath && myClaimForUri) { const { source } = myClaimForUri.value.stream; publishParams.sources = source; } publish(publishParams); } checkIsFormValid() { const { name, nameError, title, bid, bidError, tosAccepted, editingURI, isStillEditing, filePath, uploadThumbnailStatus, } = this.props; // If they are editing, they don't need a new file chosen const formValidLessFile = name && !nameError && title && bid && !bidError && tosAccepted && !(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS); return editingURI && !filePath ? isStillEditing && formValidLessFile : formValidLessFile; } renderFormErrors() { const { name, nameError, title, bid, bidError, tosAccepted, editingURI, filePath, isStillEditing, uploadThumbnailStatus, } = this.props; const isFormValid = this.checkIsFormValid(); // These are extra help // If there is an error it will be presented as an inline error as well return ( !isFormValid && (
{!title &&
{__('A title is required')}
} {!name &&
{__('A URL is required')}
} {name && nameError &&
{__('The URL you created is not valid')}
} {!bid &&
{__('A bid amount is required')}
} {!!bid && bidError &&
{bidError}
} {uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS &&
{__('Please wait for thumbnail to finish uploading')}
} {!tosAccepted &&
{__('You must agree to the terms of service')}
} {!!editingURI && !isStillEditing && !filePath &&
{__('You need to reselect a file after changing the LBRY URL')}
}
) ); } render() { const { filePath, editingURI, title, thumbnail, uploadThumbnailStatus, description, language, nsfw, contentIsFree, price, channel, name, tosAccepted, updatePublishForm, bid, nameError, isResolvingUri, winningBidForClaimUri, myClaimForUri, licenseType, otherLicenseDescription, licenseUrl, copyrightNotice, uri, bidError, publishing, clearPublish, thumbnailPath, resetThumbnailStatus, isStillEditing, } = this.props; const formDisabled = (!filePath && !editingURI) || publishing; const formValid = this.checkIsFormValid(); let submitLabel; if (isStillEditing) { submitLabel = !publishing ? __('Edit') : __('Editing...'); } else { submitLabel = !publishing ? __('Publish') : __('Publishing...'); } return (
{__('Content')}
{isStillEditing ? __('Editing a claim') : __('What are you publishing?')} {' '}{__( 'Read our' )}{' '}
{(filePath || !!editingURI) && (
)}
{!!isStillEditing && name && (

{__("If you don't choose a file, the file from your existing claim")} {` "${name}" `} {__('will be used.')}

)}
updatePublishForm({ title: e.target.value })} /> updatePublishForm({ description: text })} />
{__('Thumbnail')}
{uploadThumbnailStatus === THUMBNAIL_STATUSES.API_DOWN ? ( __('Enter a URL for your thumbnail.') ) : ( {__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '}
{__('Price')}
{__('How much will this content cost?')}
updatePublishForm({ contentIsFree: true })} /> updatePublishForm({ contentIsFree: false })} /> {!contentIsFree && ( updatePublishForm({ price: newPrice })} /> )} {price.currency !== 'LBC' && (

{__( '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.' )}

)}
{__('Anonymous or under a channel?')}

{__('This is a username or handle that your content can be found under.')}{' '} {__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}

{__('Where can people find this content?')}

{__( 'The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).' )}{' '}

updatePublishForm({ nsfw: event.target.checked })} /> updatePublishForm({ language: event.target.value })} > 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 }) } />
{__('Terms of Service')}
{__('I agree to the')}{' '}
{!formDisabled && !formValid && this.renderFormErrors()}
); } } export default PublishForm;