import React from "react"; import lbry from "lbry"; import lbryuri from "lbryuri"; import FormField from "component/formField"; import { Form, FormRow, Submit } from "component/form.js"; import Link from "component/link"; import FormFieldPrice from "component/formFieldPrice"; import Modal from "modal/modal"; import { BusyMessage } from "component/common"; import ChannelSection from "./internal/channelSection"; class PublishForm extends React.PureComponent { constructor(props) { super(props); this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; this._defaultCopyrightNotice = "All rights reserved."; this._defaultPaidPrice = 0.01; this.state = { id: null, uri: null, rawName: "", name: "", bid: 10, hasFile: false, feeAmount: "", feeCurrency: "LBC", channel: "anonymous", newChannelName: "@", newChannelBid: 10, meta_title: "", meta_thumbnail: "", meta_description: "", meta_language: "en", meta_nsfw: "0", licenseType: "", copyrightNotice: this._defaultCopyrightNotice, otherLicenseDescription: "", otherLicenseUrl: "", tosAgree: false, prefillDone: false, uploadProgress: 0.0, uploaded: false, errorMessage: null, submitting: false, creatingChannel: false, modal: null, isFee: false, customUrl: false, source: null, mode: "publish", }; } _updateChannelList(channel) { const { fetchingChannels, fetchChannelListMine } = this.props; if (!fetchingChannels) fetchChannelListMine(); } handleSubmit() { this.setState({ submitting: true, }); let checkFields = this._requiredFields; if (!this.myClaimExists()) { checkFields.unshift("file"); } let missingFieldFound = false; for (let fieldName of checkFields) { const field = this.refs[fieldName]; if (field) { if (field.getValue() === "" || field.getValue() === false) { field.showRequiredError(); if (!missingFieldFound) { field.focus(); missingFieldFound = true; } } else { field.clearError(); } } } if (missingFieldFound) { this.setState({ submitting: false, }); return; } let metadata = {}; for (let metaField of ["title", "description", "thumbnail", "language"]) { const value = this.state["meta_" + metaField]; if (value) { metadata[metaField] = value; } } metadata.license = this.getLicense(); metadata.licenseUrl = this.getLicenseUrl(); metadata.nsfw = !!parseInt(this.state.meta_nsfw); var doPublish = () => { var publishArgs = { name: this.state.name, bid: parseFloat(this.state.bid), metadata: metadata, ...(this.state.channel != "new" && this.state.channel != "anonymous" ? { channel_name: this.state.channel } : {}), }; const { source } = this.state; if (this.refs.file.getValue() !== "") { publishArgs.file_path = this.refs.file.getValue(); } else if (source) { publishArgs.sources = source; } const success = claim => {}; const failure = error => this.handlePublishError(error); this.handlePublishStarted(); this.props.publish(publishArgs).then(success, failure); }; if (this.state.isFee && parseFloat(this.state.feeAmount) > 0) { lbry.wallet_unused_address().then(address => { metadata.fee = { currency: this.state.feeCurrency, amount: parseFloat(this.state.feeAmount), address: address, }; doPublish(); }); } else { doPublish(); } } handlePublishStarted() { this.setState({ modal: "publishStarted", }); } handlePublishStartedConfirmed() { this.props.navigate("/published"); } handlePublishError(error) { this.setState({ submitting: false, modal: "error", errorMessage: error.message, }); } claim() { const { claimsByUri } = this.props; const { uri } = this.state; return claimsByUri[uri]; } topClaimValue() { if (!this.claim()) return null; return parseFloat(this.claim().effective_amount); } myClaimExists() { const { myClaims } = this.props; const { name } = this.state; if (!name) return false; return !!myClaims.find(claim => claim.name === name); } handleEditClaim() { const claimInfo = this.claim() || this.myClaimInfo(); if (claimInfo) { this.handlePrefillClaim(claimInfo); } } topClaimIsMine() { const myClaimInfo = this.myClaimInfo(); const { claimsByUri } = this.props; const { uri } = this.state; if (!uri) return null; const claim = claimsByUri[uri]; if (!claim) return true; if (!myClaimInfo) return false; return myClaimInfo.amount >= claim.amount; } myClaimInfo() { const { id } = this.state; return Object.values(this.props.myClaims).find( claim => claim.claim_id === id ); } handleNameChange(event) { var rawName = event.target.value; this.setState({ customUrl: Boolean(rawName.length), }); this.nameChanged(rawName); } nameChanged(rawName) { if (!rawName) { this.setState({ rawName: "", name: "", uri: "", prefillDone: false, mode: "publish", }); return; } if (!lbryuri.isValidName(rawName, false)) { this.refs.name.showError( __("LBRY names must contain only letters, numbers and dashes.") ); return; } let channel = ""; if (this.state.channel !== "anonymous") channel = this.state.channel; const name = rawName.toLowerCase(); const uri = lbryuri.build({ contentName: name, channelName: channel }); this.setState({ rawName: rawName, name: name, prefillDone: false, mode: "publish", uri, }); if (this.resolveUriTimeout) { clearTimeout(this.resolveUriTimeout); this.resolveUriTimeout = undefined; } const resolve = () => this.props.resolveUri(uri); this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { once: true, }); } handlePrefillClaim(claimInfo) { const { claim_id, name, channel_name, amount } = claimInfo; const { source, metadata } = claimInfo.value.stream; const { license, licenseUrl, title, thumbnail, description, language, nsfw, } = metadata; let newState = { id: claim_id, channel: channel_name || "anonymous", bid: amount, meta_title: title, meta_thumbnail: thumbnail, meta_description: description, meta_language: language, meta_nsfw: nsfw, mode: "edit", prefillDone: true, rawName: name, name, source, }; if (license == this._defaultCopyrightNotice) { newState.licenseType = "copyright"; newState.copyrightNotice = this._defaultCopyrightNotice; } else { // If the license URL or description matches one of the drop-down options, use that let licenseType = "other"; // Will be overridden if we find a match for (let option of this._meta_license.getOptions()) { if ( option.getAttribute("data-url") === licenseUrl || option.text === license ) { licenseType = option.value; } } if (licenseType == "other") { newState.otherLicenseDescription = license; newState.otherLicenseUrl = licenseUrl; } newState.licenseType = licenseType; } this.setState(newState); } handleBidChange(event) { this.setState({ bid: event.target.value, }); } handleFeeChange(newValue) { this.setState({ feeAmount: newValue.amount, feeCurrency: newValue.currency, }); } handleFeePrefChange(feeEnabled) { this.setState({ isFee: feeEnabled, feeAmount: this.state.feeAmount == "" ? this._defaultPaidPrice : this.state.feeAmount, }); } handleMetadataChange(event) { /** * This function is used for all metadata inputs that store the final value directly into state. * The only exceptions are inputs related to license description and license URL, which require * more complex logic and the final value is determined at submit time. */ this.setState({ ["meta_" + event.target.name]: event.target.value, }); } handleDescriptionChanged(text) { this.setState({ meta_description: text, }); } handleLicenseTypeChange(event) { this.setState({ licenseType: event.target.value, }); } handleCopyrightNoticeChange(event) { this.setState({ copyrightNotice: event.target.value, }); } handleOtherLicenseDescriptionChange(event) { this.setState({ otherLicenseDescription: event.target.value, }); } handleOtherLicenseUrlChange(event) { this.setState({ otherLicenseUrl: event.target.value, }); } handleChannelChange(channelName) { this.setState({ mode: "publish", channel: channelName, }); const nameChanged = () => this.nameChanged(this.state.rawName); setTimeout(nameChanged.bind(this), 500, { once: true }); } handleTOSChange(event) { this.setState({ tosAgree: event.target.checked, }); } getLicense() { switch (this.state.licenseType) { case "copyright": return this.state.copyrightNotice; case "other": return this.state.otherLicenseDescription; default: return this._meta_license.getSelectedElement().text; } } getLicenseUrl() { switch (this.state.licenseType) { case "copyright": return ""; case "other": return this.state.otherLicenseUrl; default: return this._meta_license.getSelectedElement().getAttribute("data-url"); } } componentWillMount() { this.props.fetchClaimListMine(); this._updateChannelList(); const { id } = this.props.params; this.setState({ id }); } componentDidMount() { this.handleEditClaim(); } onFileChange() { const { mode } = this.state; if (this.refs.file.getValue()) { this.setState({ hasFile: true }); if (!this.state.customUrl && mode !== "edit") { let fileName = this._getFileName(this.refs.file.getValue()); this.nameChanged(fileName); } } else { this.setState({ hasFile: false }); } } _getFileName(fileName) { const path = require("path"); const extension = path.extname(fileName); fileName = path.basename(fileName, extension); fileName = fileName.replace(lbryuri.REGEXP_INVALID_URI, ""); return fileName; } getNameBidHelpText() { const { prefillDone, name, uri } = this.state; const { resolvingUris } = this.props; const claim = this.claim(); if (prefillDone) { return __("Existing claim data was prefilled"); } if (uri && resolvingUris.indexOf(uri) !== -1 && claim === undefined) { return __("Checking..."); } else if (!name) { return __("Select a URL for this publish."); } else if (!claim) { return __("This URL is unused."); } else if (this.myClaimExists() && !prefillDone) { return ( {__("You already have a claim with this name.")}{" "} this.handleEditClaim()} /> ); } else if (claim) { const topClaimValue = this.topClaimValue(); if (topClaimValue === 1) { return ( {__( 'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.', name )} ); } else { return ( {__( 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', topClaimValue, name )} ); } } else { return ""; } } closeModal() { this.setState({ modal: null, }); } render() { const { mode, submitting } = this.state; const lbcInputHelp = __( "This LBC remains yours and the deposit can be undone at any time." ); let submitLabel = !submitting ? __("Publish") : __("Publishing..."); if (mode === "edit") { submitLabel = !submitting ? __("Update") : __("Updating..."); } return (

{__("Content")}

{__("What are you publishing?")}
{ this.onFileChange(event); }} helper={ this.myClaimExists() ? __( "If you don't choose a file, the file from your existing claim will be used." ) : null } />
{!this.state.hasFile && !this.myClaimExists() ? null :
{ this.handleMetadataChange(event); }} />
{ this.handleMetadataChange(event); }} />

{__("Description")}

{ this.handleDescriptionChanged(text); }} />
{ this.handleMetadataChange(event); }} >
{ this.handleMetadataChange(event); }} > {/* */}
}

{__("Price")}

{__("How much does this content cost?")}
this.handleFeePrefChange(false)} checked={!this.state.isFee} /> { this.handleFeePrefChange(true); }} checked={this.state.isFee} /> this.handleFeeChange(val)} /> {this.state.isFee && this.state.feeCurrency.toUpperCase() != "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." )}
: null}

{__("License")}

{ this._meta_license = row; }} onChange={event => { this.handleLicenseTypeChange(event); }} > {this.state.licenseType == "copyright" ? { this.handleCopyrightNoticeChange(event); }} /> : null} {this.state.licenseType == "other" ? { this.handleOtherLicenseDescriptionChange(event); }} /> : null} {this.state.licenseType == "other" ? { this.handleOtherLicenseUrlChange(event); }} /> : null}

{__("Content URL")}

{__( "This is the exact address where people find your content (ex. lbry://myvideo)." )} {" "} .
{ this.handleNameChange(event); }} helper={this.getNameBidHelpText()} />
{this.state.rawName ?
{ this.handleBidChange(event); }} value={this.state.bid} placeholder={this.claim() ? this.topClaimValue() + 10 : 100} helper={lbcInputHelp} min="0" />
: ""}

{__("Terms of Service")}

{__("I agree to the")} {" "} } type="checkbox" checked={this.state.tosAgree} onChange={event => { this.handleTOSChange(event); }} />
{ this.handlePublishStartedConfirmed(event); }} >

{__("Your file has been published to LBRY at the address")} {" "}{this.state.uri}!

{__( 'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.' )}

{ this.closeModal(event); }} > {__( "The following error occurred when attempting to publish your file" )}: {this.state.errorMessage}
); } } export default PublishForm;